@remix-run/assets 0.2.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 +241 -28
- 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 +10 -1
- package/dist/lib/asset-server.d.ts +33 -7
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +195 -23
- 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/injected-packages.d.ts +11 -0
- package/dist/lib/injected-packages.d.ts.map +1 -0
- package/dist/lib/injected-packages.js +86 -0
- package/dist/lib/paths.d.ts +1 -0
- package/dist/lib/paths.d.ts.map +1 -1
- package/dist/lib/paths.js +12 -0
- package/dist/lib/routes.d.ts +5 -7
- package/dist/lib/routes.d.ts.map +1 -1
- package/dist/lib/routes.js +41 -36
- 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 +57 -10
- package/dist/lib/scripts/transform.d.ts.map +1 -1
- package/dist/lib/scripts/transform.js +79 -3
- 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 +11 -5
- package/src/assets.ts +1 -0
- package/src/lib/access.ts +10 -1
- package/src/lib/asset-server.ts +297 -34
- 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/injected-packages.ts +117 -0
- package/src/lib/paths.ts +28 -0
- package/src/lib/routes.ts +67 -55
- package/src/lib/scripts/compiler.ts +43 -5
- package/src/lib/scripts/resolve.ts +89 -11
- package/src/lib/scripts/transform.ts +105 -3
- 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
package/src/lib/asset-server.ts
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import * as path from 'node:path'
|
|
2
2
|
import * as fs from 'node:fs'
|
|
3
3
|
import { createAccessPolicy } from './access.ts'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
createAssetServerCompilationError,
|
|
6
|
+
isAssetServerCompilationError,
|
|
7
|
+
} from './compilation-error.ts'
|
|
8
|
+
import { createFileCompiler, createResponseForFile } from './files/compiler.ts'
|
|
9
|
+
import type { FileCompiler } from './files/compiler.ts'
|
|
10
|
+
import { normalizeFilesOptions, serializeAssetTransformInvocations } from './files/config.ts'
|
|
11
|
+
import type {
|
|
12
|
+
AssetRequestTransformMap,
|
|
13
|
+
AssetServerFilesOptions,
|
|
14
|
+
AssetTransformInvocation,
|
|
15
|
+
ResolvedAssetServerFilesOptions,
|
|
16
|
+
} from './files/config.ts'
|
|
5
17
|
import { getFingerprintRequestCacheControl, parseFingerprintSuffix } from './fingerprint.ts'
|
|
6
|
-
import {
|
|
18
|
+
import { getInjectedPackageRouteConfigs } from './injected-packages.ts'
|
|
19
|
+
import { normalizeFilePath, normalizePathname } from './paths.ts'
|
|
7
20
|
import { compileRoutes } from './routes.ts'
|
|
8
21
|
import type { CompiledRoutes } from './routes.ts'
|
|
9
22
|
import { createResponseForScript, createScriptCompiler } from './scripts/compiler.ts'
|
|
@@ -53,7 +66,13 @@ interface AssetServerScriptOptions {
|
|
|
53
66
|
|
|
54
67
|
const scriptExtensionSet = new Set<string>(supportedScriptExtensions)
|
|
55
68
|
|
|
56
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Options used to construct an {@link AssetServer} via {@link createAssetServer}.
|
|
71
|
+
*/
|
|
72
|
+
export interface AssetServerOptions<transforms extends AssetRequestTransformMap = {}> {
|
|
73
|
+
/** Public mount path for this asset server, e.g. `'/assets'`. */
|
|
74
|
+
basePath: string
|
|
75
|
+
/** File patterns keyed by public URL patterns relative to `basePath`. */
|
|
57
76
|
/** File patterns keyed by public URL patterns. */
|
|
58
77
|
fileMap: Readonly<Record<string, string>>
|
|
59
78
|
/**
|
|
@@ -100,6 +119,12 @@ export interface AssetServerOptions {
|
|
|
100
119
|
* Script-only configuration.
|
|
101
120
|
*/
|
|
102
121
|
scripts?: AssetServerScriptOptions
|
|
122
|
+
/**
|
|
123
|
+
* Leaf file asset configuration. Files configured here are served directly and can be
|
|
124
|
+
* referenced from CSS `url(...)` rules. Compiled asset extensions like `.css` and script
|
|
125
|
+
* module extensions are not allowed here.
|
|
126
|
+
*/
|
|
127
|
+
files?: AssetServerFilesOptions<transforms>
|
|
103
128
|
/**
|
|
104
129
|
* Enable filesystem-backed cache invalidation for long-lived server instances.
|
|
105
130
|
* Enabled by default. Pass `true` to use the default watcher options, an options
|
|
@@ -113,7 +138,26 @@ export interface AssetServerOptions {
|
|
|
113
138
|
onError?: (error: unknown) => void | Response | Promise<void | Response>
|
|
114
139
|
}
|
|
115
140
|
|
|
116
|
-
|
|
141
|
+
type AssetServerCreateOptions<transforms extends AssetRequestTransformMap> = Omit<
|
|
142
|
+
AssetServerOptions<transforms>,
|
|
143
|
+
'files'
|
|
144
|
+
> & {
|
|
145
|
+
files?: Omit<AssetServerFilesOptions<transforms>, 'transforms'> & {
|
|
146
|
+
transforms?: transforms
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export type AssetServerGetHrefOptions<transforms extends AssetRequestTransformMap> =
|
|
151
|
+
| undefined
|
|
152
|
+
| {
|
|
153
|
+
transform: readonly AssetTransformInvocation<transforms>[]
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Serves compiled scripts and styles for asset requests routed to it.
|
|
158
|
+
* Construct with {@link createAssetServer}.
|
|
159
|
+
*/
|
|
160
|
+
export interface AssetServer<transforms extends AssetRequestTransformMap = {}> {
|
|
117
161
|
/**
|
|
118
162
|
* Serves a script or style request. Returns `Response | null` — null means the request
|
|
119
163
|
* was not handled by this server, letting the router fall through to a 404.
|
|
@@ -122,7 +166,7 @@ export interface AssetServer {
|
|
|
122
166
|
/**
|
|
123
167
|
* Returns the request href for a served asset file.
|
|
124
168
|
*/
|
|
125
|
-
getHref(filePath: string): Promise<string>
|
|
169
|
+
getHref(filePath: string, options?: AssetServerGetHrefOptions<transforms>): Promise<string>
|
|
126
170
|
/**
|
|
127
171
|
* Returns preload URLs for one or more served asset files, ordered shallowest-first.
|
|
128
172
|
*/
|
|
@@ -133,12 +177,14 @@ export interface AssetServer {
|
|
|
133
177
|
close(): Promise<void>
|
|
134
178
|
}
|
|
135
179
|
|
|
136
|
-
type ResolvedAssetServerOptions = {
|
|
180
|
+
type ResolvedAssetServerOptions<transforms extends AssetRequestTransformMap> = {
|
|
137
181
|
allow: readonly string[]
|
|
182
|
+
basePath: string
|
|
138
183
|
buildId?: string
|
|
139
184
|
define?: Record<string, string>
|
|
140
185
|
deny?: readonly string[]
|
|
141
186
|
external: string[]
|
|
187
|
+
files: ResolvedAssetServerFilesOptions
|
|
142
188
|
fingerprintAssets: boolean
|
|
143
189
|
minify: boolean
|
|
144
190
|
onError: NonNullable<AssetServerOptions['onError']>
|
|
@@ -151,14 +197,18 @@ type ResolvedAssetServerOptions = {
|
|
|
151
197
|
watchOptions: AssetServerWatchOptions | null
|
|
152
198
|
}
|
|
153
199
|
|
|
154
|
-
const chokidarWatcherByAssetServer = new WeakMap<
|
|
155
|
-
const watcherByAssetServer = new WeakMap<
|
|
200
|
+
const chokidarWatcherByAssetServer = new WeakMap<object, ChokidarWatcher>()
|
|
201
|
+
const watcherByAssetServer = new WeakMap<object, AssetServerWatcher>()
|
|
156
202
|
|
|
157
|
-
export function getInternalChokidarWatcher
|
|
203
|
+
export function getInternalChokidarWatcher<transforms extends AssetRequestTransformMap>(
|
|
204
|
+
assetServer: AssetServer<transforms>,
|
|
205
|
+
): ChokidarWatcher | undefined {
|
|
158
206
|
return chokidarWatcherByAssetServer.get(assetServer)
|
|
159
207
|
}
|
|
160
208
|
|
|
161
|
-
export function getInternalWatchTargets
|
|
209
|
+
export function getInternalWatchTargets<transforms extends AssetRequestTransformMap>(
|
|
210
|
+
assetServer: AssetServer<transforms>,
|
|
211
|
+
): readonly string[] {
|
|
162
212
|
return watcherByAssetServer.get(assetServer)?.getWatchedTargets() ?? []
|
|
163
213
|
}
|
|
164
214
|
|
|
@@ -174,8 +224,9 @@ export function getInternalWatchTargets(assetServer: AssetServer): readonly stri
|
|
|
174
224
|
* @example
|
|
175
225
|
* ```ts
|
|
176
226
|
* let assetServer = createAssetServer({
|
|
227
|
+
* basePath: '/assets',
|
|
177
228
|
* fileMap: {
|
|
178
|
-
* '/
|
|
229
|
+
* '/app/*path': 'app/*path',
|
|
179
230
|
* },
|
|
180
231
|
* allow: ['app/**'],
|
|
181
232
|
* })
|
|
@@ -183,7 +234,9 @@ export function getInternalWatchTargets(assetServer: AssetServer): readonly stri
|
|
|
183
234
|
* route('/assets/*path', ({ request }) => assetServer.fetch(request))
|
|
184
235
|
* ```
|
|
185
236
|
*/
|
|
186
|
-
export function createAssetServer
|
|
237
|
+
export function createAssetServer<const transforms extends AssetRequestTransformMap = {}>(
|
|
238
|
+
options: AssetServerCreateOptions<transforms>,
|
|
239
|
+
): AssetServer<transforms> {
|
|
187
240
|
let resolvedOptions = resolveAssetServerOptions(options)
|
|
188
241
|
let accessPolicy = createAccessPolicy({
|
|
189
242
|
allow: resolvedOptions.allow,
|
|
@@ -192,12 +245,14 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
|
|
|
192
245
|
})
|
|
193
246
|
let watcher: AssetServerWatcher | null = null
|
|
194
247
|
let chokidarWatcher: ChokidarWatcher | null = null
|
|
248
|
+
let fileCompiler: FileCompiler | null = null
|
|
195
249
|
let scriptCompiler = createScriptCompiler({
|
|
196
250
|
buildId: resolvedOptions.buildId,
|
|
197
251
|
define: resolvedOptions.define,
|
|
198
252
|
external: resolvedOptions.external,
|
|
199
253
|
fingerprintAssets: resolvedOptions.fingerprintAssets,
|
|
200
254
|
isAllowed: accessPolicy.isAllowed,
|
|
255
|
+
isDenied: accessPolicy.isDenied,
|
|
201
256
|
minify: resolvedOptions.minify,
|
|
202
257
|
onWatchDirectoriesChange: (delta) => {
|
|
203
258
|
if (!watcher) return
|
|
@@ -214,11 +269,27 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
|
|
|
214
269
|
let styleCompiler = createStyleCompiler({
|
|
215
270
|
buildId: resolvedOptions.buildId,
|
|
216
271
|
fingerprintAssets: resolvedOptions.fingerprintAssets,
|
|
272
|
+
getServedFileUrl: (identityPath: string, options: { transform: readonly string[] | null }) => {
|
|
273
|
+
if (!fileCompiler) {
|
|
274
|
+
throw createAssetServerCompilationError(`File type is not supported: ${identityPath}`, {
|
|
275
|
+
code: 'FILE_NOT_SUPPORTED',
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (options.transform !== null) {
|
|
280
|
+
fileCompiler.validateTransformQuery(options.transform)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return fileCompiler.getHref(identityPath, { transform: options.transform })
|
|
284
|
+
},
|
|
217
285
|
isAllowed: accessPolicy.isAllowed,
|
|
286
|
+
isServedFilePath(filePath) {
|
|
287
|
+
return fileCompiler?.isServedFilePath(filePath) ?? false
|
|
288
|
+
},
|
|
218
289
|
minify: resolvedOptions.minify,
|
|
219
290
|
onWatchDirectoriesChange: (delta) => {
|
|
220
291
|
if (!watcher) return
|
|
221
|
-
watcher.updateWatchedDirectories(delta)
|
|
292
|
+
watcher.updateWatchedDirectories(delta, { includeAncestors: false })
|
|
222
293
|
},
|
|
223
294
|
rootDir: resolvedOptions.rootDir,
|
|
224
295
|
routes: resolvedOptions.routes,
|
|
@@ -227,6 +298,20 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
|
|
|
227
298
|
targets: resolvedOptions.stylesTarget,
|
|
228
299
|
watchIgnore: resolvedOptions.watchOptions?.ignore,
|
|
229
300
|
})
|
|
301
|
+
if (resolvedOptions.files.extensions.length > 0) {
|
|
302
|
+
fileCompiler = createFileCompiler({
|
|
303
|
+
buildId: resolvedOptions.buildId,
|
|
304
|
+
cache: resolvedOptions.files.cache,
|
|
305
|
+
extensions: resolvedOptions.files.extensions,
|
|
306
|
+
fingerprintAssets: resolvedOptions.fingerprintAssets,
|
|
307
|
+
globalTransforms: resolvedOptions.files.globalTransforms,
|
|
308
|
+
isAllowed: accessPolicy.isAllowed,
|
|
309
|
+
maxRequestTransforms: resolvedOptions.files.maxRequestTransforms,
|
|
310
|
+
transforms: resolvedOptions.files.transforms,
|
|
311
|
+
rootDir: resolvedOptions.rootDir,
|
|
312
|
+
routes: resolvedOptions.routes,
|
|
313
|
+
})
|
|
314
|
+
}
|
|
230
315
|
if (resolvedOptions.watchOptions) {
|
|
231
316
|
watcher = createAssetServerWatcher({
|
|
232
317
|
...resolvedOptions.watchOptions,
|
|
@@ -252,16 +337,19 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
|
|
|
252
337
|
let normalizedFilePath = normalizeFilePath(filePath)
|
|
253
338
|
await scriptCompiler.handleFileEvent(normalizedFilePath, event)
|
|
254
339
|
await styleCompiler.handleFileEvent(normalizedFilePath, event)
|
|
340
|
+
await fileCompiler?.handleFileEvent(normalizedFilePath, event)
|
|
255
341
|
} catch (error) {
|
|
256
342
|
console.error(`There was an error invalidating the asset server cache: ${error}`)
|
|
257
343
|
}
|
|
258
344
|
}
|
|
259
345
|
|
|
260
|
-
let assetServer: AssetServer = {
|
|
346
|
+
let assetServer: AssetServer<transforms> = {
|
|
261
347
|
async fetch(request) {
|
|
262
348
|
if (request.method !== 'GET' && request.method !== 'HEAD') return null
|
|
263
349
|
|
|
264
|
-
let
|
|
350
|
+
let requestUrl = new URL(request.url)
|
|
351
|
+
let requestedTransform = normalizeRequestedTransformQuery(requestUrl.searchParams)
|
|
352
|
+
let parsedRequestPathname = parseAssetRequestPathname(requestUrl.pathname, {
|
|
265
353
|
fingerprintAssets: resolvedOptions.fingerprintAssets,
|
|
266
354
|
routes: resolvedOptions.routes,
|
|
267
355
|
})
|
|
@@ -271,6 +359,11 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
|
|
|
271
359
|
let ifNoneMatch = request.headers.get('If-None-Match')
|
|
272
360
|
|
|
273
361
|
if (isStyleFilePath(parsedRequestPathname.filePath)) {
|
|
362
|
+
if (requestedTransform !== null) {
|
|
363
|
+
return badRequest(
|
|
364
|
+
'Asset request transforms are only supported for configured file assets',
|
|
365
|
+
)
|
|
366
|
+
}
|
|
274
367
|
let styleResult = await styleCompiler.getStyle(parsedRequestPathname.filePath, {
|
|
275
368
|
ifNoneMatch,
|
|
276
369
|
isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
|
|
@@ -299,9 +392,43 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
|
|
|
299
392
|
}
|
|
300
393
|
|
|
301
394
|
if (!isScriptFilePath(parsedRequestPathname.filePath)) {
|
|
302
|
-
|
|
395
|
+
if (!fileCompiler?.isServedFilePath(parsedRequestPathname.filePath)) {
|
|
396
|
+
return null
|
|
397
|
+
}
|
|
398
|
+
if (parsedRequestPathname.isSourceMapRequest) {
|
|
399
|
+
return null
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
let fileResult = await fileCompiler.getFile(parsedRequestPathname.filePath, {
|
|
403
|
+
ifNoneMatch,
|
|
404
|
+
requestedFingerprint: parsedRequestPathname.requestedFingerprint,
|
|
405
|
+
transform: requestedTransform,
|
|
406
|
+
})
|
|
407
|
+
if (fileResult.type === 'not-modified') {
|
|
408
|
+
return new Response(null, {
|
|
409
|
+
status: 304,
|
|
410
|
+
headers: { ETag: fileResult.etag },
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (parsedRequestPathname.requestedFingerprint !== null) {
|
|
415
|
+
if (fileResult.file.fingerprint !== parsedRequestPathname.requestedFingerprint) {
|
|
416
|
+
return null
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return createResponseForFile(fileResult.file, {
|
|
421
|
+
cacheControl: parsedRequestPathname.cacheControl,
|
|
422
|
+
ifNoneMatch,
|
|
423
|
+
method: request.method,
|
|
424
|
+
})
|
|
303
425
|
}
|
|
304
426
|
|
|
427
|
+
if (requestedTransform !== null) {
|
|
428
|
+
return badRequest(
|
|
429
|
+
'Asset request transforms are only supported for configured file assets',
|
|
430
|
+
)
|
|
431
|
+
}
|
|
305
432
|
let scriptResult = await scriptCompiler.getScript(parsedRequestPathname.filePath, {
|
|
306
433
|
ifNoneMatch,
|
|
307
434
|
isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
|
|
@@ -336,52 +463,113 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
|
|
|
336
463
|
) {
|
|
337
464
|
return null
|
|
338
465
|
}
|
|
466
|
+
if (isAssetServerCompilationError(error) && error.code === 'FILE_TRANSFORM_QUERY_INVALID') {
|
|
467
|
+
return new Response(error.message, {
|
|
468
|
+
status: 400,
|
|
469
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
470
|
+
})
|
|
471
|
+
}
|
|
339
472
|
|
|
340
473
|
return responseForError(error)
|
|
341
474
|
}
|
|
342
475
|
},
|
|
343
476
|
|
|
344
|
-
async getHref(filePath) {
|
|
345
|
-
|
|
477
|
+
async getHref(filePath, hrefOptions) {
|
|
478
|
+
let transform = getHrefTransform(hrefOptions, resolvedOptions.files)
|
|
479
|
+
let typeCheckFilePath = stripFilePathUrlSuffix(filePath)
|
|
480
|
+
|
|
481
|
+
if (isStyleFilePath(typeCheckFilePath)) {
|
|
482
|
+
if (transform !== null) {
|
|
483
|
+
throw new TypeError(
|
|
484
|
+
'assetServer.getHref() only supports transforms for configured file assets',
|
|
485
|
+
)
|
|
486
|
+
}
|
|
346
487
|
return styleCompiler.getHref(filePath)
|
|
347
488
|
}
|
|
348
489
|
|
|
490
|
+
if (fileCompiler?.isServedFilePath(typeCheckFilePath)) {
|
|
491
|
+
return fileCompiler.getHref(filePath, {
|
|
492
|
+
transform,
|
|
493
|
+
})
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (!isScriptFilePath(typeCheckFilePath)) {
|
|
497
|
+
throw createAssetServerCompilationError(`File type is not supported: ${filePath}`, {
|
|
498
|
+
code: 'FILE_NOT_SUPPORTED',
|
|
499
|
+
})
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (transform !== null) {
|
|
503
|
+
throw new TypeError(
|
|
504
|
+
'assetServer.getHref() only supports transforms for configured file assets',
|
|
505
|
+
)
|
|
506
|
+
}
|
|
507
|
+
|
|
349
508
|
return scriptCompiler.getHref(filePath)
|
|
350
509
|
},
|
|
351
510
|
async getPreloads(filePath) {
|
|
352
511
|
let filePaths = Array.isArray(filePath) ? filePath : [filePath]
|
|
512
|
+
let fileAssetFiles: string[] = []
|
|
353
513
|
let styleFiles: string[] = []
|
|
354
514
|
let scriptFiles: string[] = []
|
|
515
|
+
let groupedAssetTypes: ('script' | 'style' | 'file')[] = []
|
|
355
516
|
|
|
356
517
|
for (let nextFilePath of filePaths) {
|
|
357
|
-
|
|
518
|
+
let typeCheckFilePath = stripFilePathUrlSuffix(nextFilePath)
|
|
519
|
+
|
|
520
|
+
if (isStyleFilePath(typeCheckFilePath)) {
|
|
521
|
+
if (!groupedAssetTypes.includes('style')) {
|
|
522
|
+
groupedAssetTypes.push('style')
|
|
523
|
+
}
|
|
358
524
|
styleFiles.push(nextFilePath)
|
|
359
525
|
continue
|
|
360
526
|
}
|
|
361
527
|
|
|
528
|
+
if (fileCompiler?.isServedFilePath(typeCheckFilePath)) {
|
|
529
|
+
if (!groupedAssetTypes.includes('file')) {
|
|
530
|
+
groupedAssetTypes.push('file')
|
|
531
|
+
}
|
|
532
|
+
fileAssetFiles.push(nextFilePath)
|
|
533
|
+
continue
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (!groupedAssetTypes.includes('script')) {
|
|
537
|
+
groupedAssetTypes.push('script')
|
|
538
|
+
}
|
|
362
539
|
scriptFiles.push(nextFilePath)
|
|
363
540
|
}
|
|
364
541
|
|
|
365
|
-
if (styleFiles.length === 0 && scriptFiles.length === 0) {
|
|
542
|
+
if (styleFiles.length === 0 && scriptFiles.length === 0 && fileAssetFiles.length === 0) {
|
|
366
543
|
return []
|
|
367
544
|
}
|
|
368
545
|
|
|
369
|
-
if (styleFiles.length === 0) {
|
|
546
|
+
if (styleFiles.length === 0 && fileAssetFiles.length === 0) {
|
|
370
547
|
return flattenPreloadLayers(await scriptCompiler.getPreloadLayers(filePath))
|
|
371
548
|
}
|
|
372
549
|
|
|
373
|
-
if (scriptFiles.length === 0) {
|
|
550
|
+
if (scriptFiles.length === 0 && fileAssetFiles.length === 0) {
|
|
374
551
|
return flattenPreloadLayers(await styleCompiler.getPreloadLayers(filePath))
|
|
375
552
|
}
|
|
376
553
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
554
|
+
if (scriptFiles.length === 0 && styleFiles.length === 0) {
|
|
555
|
+
return flattenPreloadLayers(await getFilePreloadLayers(fileCompiler, fileAssetFiles))
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Mixed asset type preloads need to be merged, so we merge in order of first asset type seen.
|
|
559
|
+
let preloadLayerGroupPromises = groupedAssetTypes.flatMap((assetType) => {
|
|
560
|
+
switch (assetType) {
|
|
561
|
+
case 'script':
|
|
562
|
+
return scriptFiles.length > 0 ? [scriptCompiler.getPreloadLayers(scriptFiles)] : []
|
|
563
|
+
case 'style':
|
|
564
|
+
return styleFiles.length > 0 ? [styleCompiler.getPreloadLayers(styleFiles)] : []
|
|
565
|
+
case 'file':
|
|
566
|
+
return fileAssetFiles.length > 0
|
|
567
|
+
? [getFilePreloadLayers(fileCompiler, fileAssetFiles)]
|
|
568
|
+
: []
|
|
569
|
+
}
|
|
570
|
+
})
|
|
383
571
|
|
|
384
|
-
return mergePreloadLayers(await Promise.all(
|
|
572
|
+
return mergePreloadLayers(await Promise.all(preloadLayerGroupPromises))
|
|
385
573
|
},
|
|
386
574
|
async close() {
|
|
387
575
|
await watcher?.close()
|
|
@@ -398,6 +586,24 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
|
|
|
398
586
|
return assetServer
|
|
399
587
|
}
|
|
400
588
|
|
|
589
|
+
function getHrefTransform<transforms extends AssetRequestTransformMap>(
|
|
590
|
+
options: AssetServerGetHrefOptions<transforms> | undefined,
|
|
591
|
+
files: ResolvedAssetServerFilesOptions,
|
|
592
|
+
): readonly string[] | null {
|
|
593
|
+
if (!options) return null
|
|
594
|
+
let transform = options.transform
|
|
595
|
+
if (transform.length === 0) {
|
|
596
|
+
return null
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return serializeAssetTransformInvocations(transform, files.transforms, files.maxRequestTransforms)
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function normalizeRequestedTransformQuery(searchParams: URLSearchParams): readonly string[] | null {
|
|
603
|
+
let transforms = searchParams.getAll('transform')
|
|
604
|
+
return transforms.length > 0 ? transforms : null
|
|
605
|
+
}
|
|
606
|
+
|
|
401
607
|
function internalServerError(): Response {
|
|
402
608
|
return new Response('Internal Server Error', {
|
|
403
609
|
status: 500,
|
|
@@ -405,6 +611,13 @@ function internalServerError(): Response {
|
|
|
405
611
|
})
|
|
406
612
|
}
|
|
407
613
|
|
|
614
|
+
function badRequest(message: string): Response {
|
|
615
|
+
return new Response(message, {
|
|
616
|
+
status: 400,
|
|
617
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
618
|
+
})
|
|
619
|
+
}
|
|
620
|
+
|
|
408
621
|
function mergePreloadLayers(preloadLayersByRoot: readonly (readonly string[][])[]): string[] {
|
|
409
622
|
let urls: string[] = []
|
|
410
623
|
let seen = new Set<string>()
|
|
@@ -427,12 +640,40 @@ function flattenPreloadLayers(preloadLayers: readonly (readonly string[])[]): st
|
|
|
427
640
|
return preloadLayers.flatMap((layer) => layer)
|
|
428
641
|
}
|
|
429
642
|
|
|
643
|
+
async function getFilePreloadLayers<transforms extends AssetRequestTransformMap>(
|
|
644
|
+
fileCompiler: FileCompiler | null,
|
|
645
|
+
filePaths: readonly string[],
|
|
646
|
+
): Promise<string[][]> {
|
|
647
|
+
if (filePaths.length === 0) {
|
|
648
|
+
return []
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (!fileCompiler) {
|
|
652
|
+
throw createAssetServerCompilationError('File type is not supported', {
|
|
653
|
+
code: 'FILE_NOT_SUPPORTED',
|
|
654
|
+
})
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return [
|
|
658
|
+
await Promise.all(
|
|
659
|
+
filePaths.map((filePath) =>
|
|
660
|
+
fileCompiler.getHref(filePath, {
|
|
661
|
+
transform: null,
|
|
662
|
+
}),
|
|
663
|
+
),
|
|
664
|
+
),
|
|
665
|
+
]
|
|
666
|
+
}
|
|
667
|
+
|
|
430
668
|
function defaultErrorHandler(error: unknown): void {
|
|
431
669
|
console.error(error)
|
|
432
670
|
}
|
|
433
671
|
|
|
434
|
-
function resolveAssetServerOptions
|
|
672
|
+
function resolveAssetServerOptions<transforms extends AssetRequestTransformMap>(
|
|
673
|
+
options: AssetServerCreateOptions<transforms>,
|
|
674
|
+
): ResolvedAssetServerOptions<transforms> {
|
|
435
675
|
let rootDir = normalizeFilePath(fs.realpathSync(path.resolve(options.rootDir ?? process.cwd())))
|
|
676
|
+
let basePath = normalizeBasePath(options.basePath)
|
|
436
677
|
let scriptOptions = options.scripts ?? {}
|
|
437
678
|
let fingerprintOptions = normalizeFingerprintOptions({
|
|
438
679
|
fingerprint: options.fingerprint,
|
|
@@ -441,18 +682,23 @@ function resolveAssetServerOptions(options: AssetServerOptions): ResolvedAssetSe
|
|
|
441
682
|
|
|
442
683
|
return {
|
|
443
684
|
allow: options.allow,
|
|
685
|
+
basePath,
|
|
444
686
|
buildId: fingerprintOptions.buildId,
|
|
445
687
|
define: scriptOptions.define,
|
|
446
688
|
deny: options.deny,
|
|
447
689
|
external: scriptOptions.external ?? [],
|
|
690
|
+
files: normalizeFilesOptions(options.files),
|
|
448
691
|
fingerprintAssets: fingerprintOptions.enabled,
|
|
449
692
|
minify: options.minify ?? false,
|
|
450
693
|
onError: options.onError ?? defaultErrorHandler,
|
|
451
694
|
rootDir,
|
|
452
|
-
routes: compileRoutes(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
695
|
+
routes: compileRoutes(basePath, [
|
|
696
|
+
{
|
|
697
|
+
fileMap: options.fileMap,
|
|
698
|
+
rootDir,
|
|
699
|
+
},
|
|
700
|
+
...getInjectedPackageRouteConfigs(),
|
|
701
|
+
]),
|
|
456
702
|
sourceMapSourcePaths: options.sourceMapSourcePaths ?? 'url',
|
|
457
703
|
sourceMaps: options.sourceMaps,
|
|
458
704
|
scriptsTarget: resolveScriptTarget(options.target),
|
|
@@ -461,6 +707,14 @@ function resolveAssetServerOptions(options: AssetServerOptions): ResolvedAssetSe
|
|
|
461
707
|
}
|
|
462
708
|
}
|
|
463
709
|
|
|
710
|
+
function normalizeBasePath(basePath: string): string {
|
|
711
|
+
if (typeof basePath !== 'string') {
|
|
712
|
+
throw new TypeError('basePath must be a string')
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
return normalizePathname(basePath || '/').replace(/\/+$/, '') || '/'
|
|
716
|
+
}
|
|
717
|
+
|
|
464
718
|
function normalizeFingerprintOptions(options: {
|
|
465
719
|
fingerprint: AssetServerOptions['fingerprint']
|
|
466
720
|
watch: AssetServerOptions['watch']
|
|
@@ -535,3 +789,12 @@ function parseAssetRequestPathname(
|
|
|
535
789
|
function isScriptFilePath(filePath: string): boolean {
|
|
536
790
|
return scriptExtensionSet.has(path.extname(filePath).toLowerCase())
|
|
537
791
|
}
|
|
792
|
+
|
|
793
|
+
function stripFilePathUrlSuffix(filePath: string): string {
|
|
794
|
+
if (filePath.startsWith('file://')) {
|
|
795
|
+
return new URL(filePath).pathname
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
let queryIndex = filePath.indexOf('?')
|
|
799
|
+
return queryIndex === -1 ? filePath : filePath.slice(0, queryIndex)
|
|
800
|
+
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
type AssetServerCompilationErrorCode =
|
|
2
2
|
| 'FILE_NOT_FOUND'
|
|
3
3
|
| 'FILE_NOT_ALLOWED'
|
|
4
|
+
| 'FILE_NOT_SUPPORTED'
|
|
4
5
|
| 'FILE_OUTSIDE_FILE_MAP'
|
|
6
|
+
| 'FILE_TRANSFORM_QUERY_INVALID'
|
|
7
|
+
| 'FILE_TRANSFORM_NOT_SUPPORTED'
|
|
8
|
+
| 'FILE_TRANSFORM_RESULT_INVALID'
|
|
9
|
+
| 'FILE_TRANSFORM_FAILED'
|
|
5
10
|
| 'COMMONJS_NOT_SUPPORTED'
|
|
6
11
|
| 'TRANSFORM_FAILED'
|
|
7
12
|
| 'EMIT_FAILED'
|
|
@@ -9,6 +14,10 @@ type AssetServerCompilationErrorCode =
|
|
|
9
14
|
| 'IMPORT_NOT_SUPPORTED'
|
|
10
15
|
| 'IMPORT_NOT_ALLOWED'
|
|
11
16
|
| 'IMPORT_OUTSIDE_FILE_MAP'
|
|
17
|
+
| 'URL_RESOLUTION_FAILED'
|
|
18
|
+
| 'URL_NOT_SUPPORTED'
|
|
19
|
+
| 'URL_NOT_ALLOWED'
|
|
20
|
+
| 'URL_OUTSIDE_FILE_MAP'
|
|
12
21
|
|
|
13
22
|
/**
|
|
14
23
|
* Internal error used by the request-time asset compilation pipeline.
|