@remix-run/assets 0.1.0 → 0.3.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 +88 -67
- package/dist/lib/access.d.ts.map +1 -1
- package/dist/lib/access.js +3 -0
- package/dist/lib/asset-server.d.ts +50 -45
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +159 -50
- package/dist/lib/compilation-error.d.ts +2 -2
- package/dist/lib/compilation-error.d.ts.map +1 -1
- package/dist/lib/compilation-error.js +1 -1
- package/dist/lib/file-matcher.d.ts.map +1 -1
- package/dist/lib/file-matcher.js +3 -2
- 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/module-store.d.ts +41 -0
- package/dist/lib/module-store.d.ts.map +1 -0
- package/dist/lib/{scripts/store.js → module-store.js} +43 -41
- 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 +13 -16
- package/dist/lib/scripts/compiler.d.ts +15 -15
- package/dist/lib/scripts/compiler.d.ts.map +1 -1
- package/dist/lib/scripts/compiler.js +50 -46
- package/dist/lib/scripts/emit.js +2 -2
- package/dist/lib/scripts/resolve.d.ts +7 -17
- package/dist/lib/scripts/resolve.d.ts.map +1 -1
- package/dist/lib/scripts/resolve.js +47 -14
- package/dist/lib/scripts/transform.d.ts +11 -9
- package/dist/lib/scripts/transform.d.ts.map +1 -1
- package/dist/lib/scripts/transform.js +110 -23
- package/dist/lib/source-maps.d.ts +1 -1
- package/dist/lib/source-maps.d.ts.map +1 -1
- package/dist/lib/source-maps.js +7 -1
- package/dist/lib/styles/compiler.d.ts +52 -0
- package/dist/lib/styles/compiler.d.ts.map +1 -0
- package/dist/lib/styles/compiler.js +272 -0
- package/dist/lib/styles/emit.d.ts +25 -0
- package/dist/lib/styles/emit.d.ts.map +1 -0
- package/dist/lib/styles/emit.js +78 -0
- package/dist/lib/styles/resolve.d.ts +48 -0
- package/dist/lib/styles/resolve.d.ts.map +1 -0
- package/dist/lib/styles/resolve.js +188 -0
- package/dist/lib/styles/transform.d.ts +47 -0
- package/dist/lib/styles/transform.d.ts.map +1 -0
- package/dist/lib/styles/transform.js +131 -0
- package/dist/lib/target.d.ts +21 -0
- package/dist/lib/target.d.ts.map +1 -0
- package/dist/lib/target.js +127 -0
- package/package.json +13 -4
- package/src/lib/access.ts +2 -0
- package/src/lib/asset-server.ts +239 -99
- package/src/lib/compilation-error.ts +7 -7
- package/src/lib/file-matcher.ts +3 -2
- package/src/lib/injected-packages.ts +117 -0
- package/src/lib/{scripts/store.ts → module-store.ts} +100 -87
- package/src/lib/paths.ts +28 -0
- package/src/lib/routes.ts +31 -22
- package/src/lib/scripts/compiler.ts +88 -70
- package/src/lib/scripts/emit.ts +2 -2
- package/src/lib/scripts/resolve.ts +85 -28
- package/src/lib/scripts/transform.ts +153 -36
- package/src/lib/source-maps.ts +8 -1
- package/src/lib/styles/compiler.ts +400 -0
- package/src/lib/styles/emit.ts +137 -0
- package/src/lib/styles/resolve.ts +316 -0
- package/src/lib/styles/transform.ts +226 -0
- package/src/lib/target.ts +196 -0
- package/dist/lib/scripts/store.d.ts +0 -40
- package/dist/lib/scripts/store.d.ts.map +0 -1
package/src/lib/asset-server.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import * as path from 'node:path'
|
|
2
2
|
import * as fs from 'node:fs'
|
|
3
|
-
import { isAssetServerCompilationError } from './compilation-error.ts'
|
|
4
3
|
import { createAccessPolicy } from './access.ts'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { isAssetServerCompilationError } from './compilation-error.ts'
|
|
5
|
+
import { getFingerprintRequestCacheControl, parseFingerprintSuffix } from './fingerprint.ts'
|
|
6
|
+
import { getInjectedPackageRouteConfigs } from './injected-packages.ts'
|
|
7
|
+
import { normalizeFilePath, normalizePathname } from './paths.ts'
|
|
7
8
|
import { compileRoutes } from './routes.ts'
|
|
8
9
|
import type { CompiledRoutes } from './routes.ts'
|
|
10
|
+
import { createResponseForScript, createScriptCompiler } from './scripts/compiler.ts'
|
|
11
|
+
import { supportedScriptExtensions } from './scripts/resolve.ts'
|
|
12
|
+
import { createResponseForStyle, createStyleCompiler, isStyleFilePath } from './styles/compiler.ts'
|
|
13
|
+
import { resolveScriptTarget, resolveStyleTarget } from './target.ts'
|
|
14
|
+
import type { AssetTarget, ResolvedScriptTarget, ResolvedStyleTarget } from './target.ts'
|
|
9
15
|
import { createAssetServerWatcher } from './watch.ts'
|
|
10
|
-
import type { ChokidarWatcher } from './watch.ts'
|
|
11
|
-
import type { AssetServerWatcher } from './watch.ts'
|
|
16
|
+
import type { AssetServerWatcher, ChokidarWatcher } from './watch.ts'
|
|
12
17
|
|
|
13
18
|
interface AssetServerWatchOptions {
|
|
14
19
|
/**
|
|
@@ -28,33 +33,31 @@ interface AssetServerWatchOptions {
|
|
|
28
33
|
|
|
29
34
|
interface FingerprintOptions {
|
|
30
35
|
/**
|
|
31
|
-
* Per-build invalidation token that must change whenever fingerprinted
|
|
36
|
+
* Per-build invalidation token that must change whenever fingerprinted asset URLs
|
|
32
37
|
* should be invalidated together.
|
|
33
38
|
*/
|
|
34
39
|
buildId: string
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
] as const
|
|
52
|
-
const scriptTargetSet = new Set<string>(scriptTargets)
|
|
53
|
-
|
|
54
|
-
export type ScriptsTarget = (typeof scriptTargets)[number]
|
|
42
|
+
type AssetSourceMaps = 'inline' | 'external'
|
|
43
|
+
type AssetSourceMapSourcePaths = 'url' | 'absolute'
|
|
44
|
+
|
|
45
|
+
interface AssetServerScriptOptions {
|
|
46
|
+
/**
|
|
47
|
+
* Replace global expressions with constant values during transform, e.g.
|
|
48
|
+
* `{ 'process.env.NODE_ENV': '"production"' }`
|
|
49
|
+
*/
|
|
50
|
+
define?: Record<string, string>
|
|
51
|
+
/** Import specifiers to leave unrewritten (CDN URLs, import map entries, etc.) */
|
|
52
|
+
external?: string[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const scriptExtensionSet = new Set<string>(supportedScriptExtensions)
|
|
55
56
|
|
|
56
57
|
export interface AssetServerOptions {
|
|
57
|
-
/**
|
|
58
|
+
/** Public mount path for this asset server, e.g. `'/assets'`. */
|
|
59
|
+
basePath: string
|
|
60
|
+
/** File patterns keyed by public URL patterns relative to `basePath`. */
|
|
58
61
|
fileMap: Readonly<Record<string, string>>
|
|
59
62
|
/**
|
|
60
63
|
* Root directory used to resolve relative file paths. Defaults to `process.cwd()`.
|
|
@@ -69,45 +72,37 @@ export interface AssetServerOptions {
|
|
|
69
72
|
*/
|
|
70
73
|
deny?: readonly string[]
|
|
71
74
|
/**
|
|
72
|
-
* Controls optional source-based URL fingerprinting for rewritten
|
|
75
|
+
* Controls optional source-based URL fingerprinting for rewritten asset URLs.
|
|
73
76
|
*
|
|
74
|
-
* When omitted, all served
|
|
77
|
+
* When omitted, all served assets use stable non-fingerprinted URLs with `Cache-Control: no-cache`.
|
|
75
78
|
* Cannot be used together with active watch mode. Set `watch: false` when fingerprinting.
|
|
76
79
|
*/
|
|
77
80
|
fingerprint?: FingerprintOptions
|
|
78
81
|
/**
|
|
79
|
-
*
|
|
82
|
+
* Shared compatibility target for scripts and styles. Browser targets apply to both
|
|
83
|
+
* pipelines, and `es` only affects scripts.
|
|
80
84
|
*/
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
define?: Record<string, string>
|
|
103
|
-
/**
|
|
104
|
-
* Lower emitted syntax to a specific ECMAScript target. Omit this option to preserve
|
|
105
|
-
* modern syntax unless project configuration already requests a lower target.
|
|
106
|
-
*/
|
|
107
|
-
target?: ScriptsTarget
|
|
108
|
-
/** Import specifiers to leave unrewritten (CDN URLs, import map entries, etc.) */
|
|
109
|
-
external?: string[]
|
|
110
|
-
}
|
|
85
|
+
target?: AssetTarget
|
|
86
|
+
/**
|
|
87
|
+
* Source map mode for scripts and styles.
|
|
88
|
+
* - `'external'`: serve source maps as separate `.map` files
|
|
89
|
+
* - `'inline'`: embed source maps as a base64 data URL in the compiled asset
|
|
90
|
+
*/
|
|
91
|
+
sourceMaps?: AssetSourceMaps
|
|
92
|
+
/**
|
|
93
|
+
* Source path strategy for source map `sources`.
|
|
94
|
+
* - `'url'` (default): use the stable server path (e.g. `'/assets/app/entry.ts'`)
|
|
95
|
+
* - `'absolute'`: use the original filesystem path on disk
|
|
96
|
+
*/
|
|
97
|
+
sourceMapSourcePaths?: AssetSourceMapSourcePaths
|
|
98
|
+
/**
|
|
99
|
+
* Minification setting for emitted scripts and styles.
|
|
100
|
+
*/
|
|
101
|
+
minify?: boolean
|
|
102
|
+
/**
|
|
103
|
+
* Script-only configuration.
|
|
104
|
+
*/
|
|
105
|
+
scripts?: AssetServerScriptOptions
|
|
111
106
|
/**
|
|
112
107
|
* Enable filesystem-backed cache invalidation for long-lived server instances.
|
|
113
108
|
* Enabled by default. Pass `true` to use the default watcher options, an options
|
|
@@ -123,16 +118,16 @@ export interface AssetServerOptions {
|
|
|
123
118
|
|
|
124
119
|
export interface AssetServer {
|
|
125
120
|
/**
|
|
126
|
-
* Serves a script request. Returns `Response | null` — null means the request
|
|
127
|
-
* handled by this server, letting the router fall through to a 404.
|
|
121
|
+
* Serves a script or style request. Returns `Response | null` — null means the request
|
|
122
|
+
* was not handled by this server, letting the router fall through to a 404.
|
|
128
123
|
*/
|
|
129
124
|
fetch(request: Request): Promise<Response | null>
|
|
130
125
|
/**
|
|
131
|
-
* Returns the request href for a served
|
|
126
|
+
* Returns the request href for a served asset file.
|
|
132
127
|
*/
|
|
133
128
|
getHref(filePath: string): Promise<string>
|
|
134
129
|
/**
|
|
135
|
-
* Returns preload URLs for one or more served
|
|
130
|
+
* Returns preload URLs for one or more served asset files, ordered shallowest-first.
|
|
136
131
|
*/
|
|
137
132
|
getPreloads(filePath: string | readonly string[]): Promise<string[]>
|
|
138
133
|
/**
|
|
@@ -143,18 +138,20 @@ export interface AssetServer {
|
|
|
143
138
|
|
|
144
139
|
type ResolvedAssetServerOptions = {
|
|
145
140
|
allow: readonly string[]
|
|
141
|
+
basePath: string
|
|
146
142
|
buildId?: string
|
|
147
143
|
define?: Record<string, string>
|
|
148
144
|
deny?: readonly string[]
|
|
149
145
|
external: string[]
|
|
150
|
-
|
|
146
|
+
fingerprintAssets: boolean
|
|
151
147
|
minify: boolean
|
|
152
148
|
onError: NonNullable<AssetServerOptions['onError']>
|
|
153
149
|
rootDir: string
|
|
154
150
|
routes: CompiledRoutes
|
|
155
151
|
sourceMapSourcePaths: 'url' | 'absolute'
|
|
156
152
|
sourceMaps?: 'inline' | 'external'
|
|
157
|
-
scriptsTarget?:
|
|
153
|
+
scriptsTarget?: ResolvedScriptTarget
|
|
154
|
+
stylesTarget?: ResolvedStyleTarget
|
|
158
155
|
watchOptions: AssetServerWatchOptions | null
|
|
159
156
|
}
|
|
160
157
|
|
|
@@ -172,8 +169,8 @@ export function getInternalWatchTargets(assetServer: AssetServer): readonly stri
|
|
|
172
169
|
/**
|
|
173
170
|
* Create an asset server instance
|
|
174
171
|
*
|
|
175
|
-
* Compiles TypeScript/JavaScript
|
|
176
|
-
* fingerprinting, caching, and configurable file mapping.
|
|
172
|
+
* Compiles TypeScript/JavaScript scripts and CSS styles on demand with optional
|
|
173
|
+
* source-based URL fingerprinting, caching, and configurable file mapping.
|
|
177
174
|
*
|
|
178
175
|
* @param options Server configuration
|
|
179
176
|
* @returns A {@link AssetServer} with `fetch()`, `getHref()`, and `getPreloads()` methods
|
|
@@ -181,8 +178,9 @@ export function getInternalWatchTargets(assetServer: AssetServer): readonly stri
|
|
|
181
178
|
* @example
|
|
182
179
|
* ```ts
|
|
183
180
|
* let assetServer = createAssetServer({
|
|
181
|
+
* basePath: '/assets',
|
|
184
182
|
* fileMap: {
|
|
185
|
-
* '/
|
|
183
|
+
* '/app/*path': 'app/*path',
|
|
186
184
|
* },
|
|
187
185
|
* allow: ['app/**'],
|
|
188
186
|
* })
|
|
@@ -199,11 +197,11 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
|
|
|
199
197
|
})
|
|
200
198
|
let watcher: AssetServerWatcher | null = null
|
|
201
199
|
let chokidarWatcher: ChokidarWatcher | null = null
|
|
202
|
-
let
|
|
200
|
+
let scriptCompiler = createScriptCompiler({
|
|
203
201
|
buildId: resolvedOptions.buildId,
|
|
204
202
|
define: resolvedOptions.define,
|
|
205
203
|
external: resolvedOptions.external,
|
|
206
|
-
|
|
204
|
+
fingerprintAssets: resolvedOptions.fingerprintAssets,
|
|
207
205
|
isAllowed: accessPolicy.isAllowed,
|
|
208
206
|
minify: resolvedOptions.minify,
|
|
209
207
|
onWatchDirectoriesChange: (delta) => {
|
|
@@ -218,6 +216,22 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
|
|
|
218
216
|
watchIgnore: resolvedOptions.watchOptions?.ignore,
|
|
219
217
|
watchMode: resolvedOptions.watchOptions !== null,
|
|
220
218
|
})
|
|
219
|
+
let styleCompiler = createStyleCompiler({
|
|
220
|
+
buildId: resolvedOptions.buildId,
|
|
221
|
+
fingerprintAssets: resolvedOptions.fingerprintAssets,
|
|
222
|
+
isAllowed: accessPolicy.isAllowed,
|
|
223
|
+
minify: resolvedOptions.minify,
|
|
224
|
+
onWatchDirectoriesChange: (delta) => {
|
|
225
|
+
if (!watcher) return
|
|
226
|
+
watcher.updateWatchedDirectories(delta)
|
|
227
|
+
},
|
|
228
|
+
rootDir: resolvedOptions.rootDir,
|
|
229
|
+
routes: resolvedOptions.routes,
|
|
230
|
+
sourceMapSourcePaths: resolvedOptions.sourceMapSourcePaths,
|
|
231
|
+
sourceMaps: resolvedOptions.sourceMaps,
|
|
232
|
+
targets: resolvedOptions.stylesTarget,
|
|
233
|
+
watchIgnore: resolvedOptions.watchOptions?.ignore,
|
|
234
|
+
})
|
|
221
235
|
if (resolvedOptions.watchOptions) {
|
|
222
236
|
watcher = createAssetServerWatcher({
|
|
223
237
|
...resolvedOptions.watchOptions,
|
|
@@ -241,7 +255,8 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
|
|
|
241
255
|
async function handleWatchEvent(filePath: string, event: 'add' | 'change' | 'unlink') {
|
|
242
256
|
try {
|
|
243
257
|
let normalizedFilePath = normalizeFilePath(filePath)
|
|
244
|
-
await
|
|
258
|
+
await scriptCompiler.handleFileEvent(normalizedFilePath, event)
|
|
259
|
+
await styleCompiler.handleFileEvent(normalizedFilePath, event)
|
|
245
260
|
} catch (error) {
|
|
246
261
|
console.error(`There was an error invalidating the asset server cache: ${error}`)
|
|
247
262
|
}
|
|
@@ -251,30 +266,66 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
|
|
|
251
266
|
async fetch(request) {
|
|
252
267
|
if (request.method !== 'GET' && request.method !== 'HEAD') return null
|
|
253
268
|
|
|
254
|
-
let parsedRequestPathname =
|
|
269
|
+
let parsedRequestPathname = parseAssetRequestPathname(new URL(request.url).pathname, {
|
|
270
|
+
fingerprintAssets: resolvedOptions.fingerprintAssets,
|
|
271
|
+
routes: resolvedOptions.routes,
|
|
272
|
+
})
|
|
255
273
|
if (!parsedRequestPathname) return null
|
|
256
274
|
|
|
257
275
|
try {
|
|
258
276
|
let ifNoneMatch = request.headers.get('If-None-Match')
|
|
259
|
-
|
|
277
|
+
|
|
278
|
+
if (isStyleFilePath(parsedRequestPathname.filePath)) {
|
|
279
|
+
let styleResult = await styleCompiler.getStyle(parsedRequestPathname.filePath, {
|
|
280
|
+
ifNoneMatch,
|
|
281
|
+
isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
|
|
282
|
+
requestedFingerprint: parsedRequestPathname.requestedFingerprint,
|
|
283
|
+
})
|
|
284
|
+
if (styleResult.type === 'not-modified') {
|
|
285
|
+
return new Response(null, {
|
|
286
|
+
status: 304,
|
|
287
|
+
headers: { ETag: styleResult.etag },
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
let compiledStyle = styleResult.style
|
|
292
|
+
|
|
293
|
+
if (parsedRequestPathname.requestedFingerprint !== null) {
|
|
294
|
+
if (compiledStyle.fingerprint !== parsedRequestPathname.requestedFingerprint)
|
|
295
|
+
return null
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return createResponseForStyle(compiledStyle, {
|
|
299
|
+
cacheControl: parsedRequestPathname.cacheControl,
|
|
300
|
+
ifNoneMatch,
|
|
301
|
+
isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
|
|
302
|
+
method: request.method,
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!isScriptFilePath(parsedRequestPathname.filePath)) {
|
|
307
|
+
return null
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let scriptResult = await scriptCompiler.getScript(parsedRequestPathname.filePath, {
|
|
260
311
|
ifNoneMatch,
|
|
261
312
|
isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
|
|
262
313
|
requestedFingerprint: parsedRequestPathname.requestedFingerprint,
|
|
263
314
|
})
|
|
264
|
-
if (
|
|
315
|
+
if (scriptResult.type === 'not-modified') {
|
|
265
316
|
return new Response(null, {
|
|
266
317
|
status: 304,
|
|
267
|
-
headers: { ETag:
|
|
318
|
+
headers: { ETag: scriptResult.etag },
|
|
268
319
|
})
|
|
269
320
|
}
|
|
270
321
|
|
|
271
|
-
let
|
|
322
|
+
let compiledScript = scriptResult.script
|
|
272
323
|
|
|
273
324
|
if (parsedRequestPathname.requestedFingerprint !== null) {
|
|
274
|
-
if (
|
|
325
|
+
if (compiledScript.fingerprint !== parsedRequestPathname.requestedFingerprint) return null
|
|
275
326
|
}
|
|
276
327
|
|
|
277
|
-
return
|
|
328
|
+
return createResponseForScript(compiledScript, {
|
|
278
329
|
cacheControl: parsedRequestPathname.cacheControl,
|
|
279
330
|
ifNoneMatch,
|
|
280
331
|
isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
|
|
@@ -286,7 +337,7 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
|
|
|
286
337
|
// handled here" so the outer router can continue to its own 404 behavior.
|
|
287
338
|
if (
|
|
288
339
|
isAssetServerCompilationError(error) &&
|
|
289
|
-
(error.code === '
|
|
340
|
+
(error.code === 'FILE_NOT_FOUND' || error.code === 'FILE_NOT_ALLOWED')
|
|
290
341
|
) {
|
|
291
342
|
return null
|
|
292
343
|
}
|
|
@@ -296,10 +347,46 @@ export function createAssetServer(options: AssetServerOptions): AssetServer {
|
|
|
296
347
|
},
|
|
297
348
|
|
|
298
349
|
async getHref(filePath) {
|
|
299
|
-
|
|
350
|
+
if (isStyleFilePath(filePath)) {
|
|
351
|
+
return styleCompiler.getHref(filePath)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return scriptCompiler.getHref(filePath)
|
|
300
355
|
},
|
|
301
356
|
async getPreloads(filePath) {
|
|
302
|
-
|
|
357
|
+
let filePaths = Array.isArray(filePath) ? filePath : [filePath]
|
|
358
|
+
let styleFiles: string[] = []
|
|
359
|
+
let scriptFiles: string[] = []
|
|
360
|
+
|
|
361
|
+
for (let nextFilePath of filePaths) {
|
|
362
|
+
if (isStyleFilePath(nextFilePath)) {
|
|
363
|
+
styleFiles.push(nextFilePath)
|
|
364
|
+
continue
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
scriptFiles.push(nextFilePath)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (styleFiles.length === 0 && scriptFiles.length === 0) {
|
|
371
|
+
return []
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (styleFiles.length === 0) {
|
|
375
|
+
return flattenPreloadLayers(await scriptCompiler.getPreloadLayers(filePath))
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (scriptFiles.length === 0) {
|
|
379
|
+
return flattenPreloadLayers(await styleCompiler.getPreloadLayers(filePath))
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Mixed asset type preloads need to be merged, so we merge in order of first asset type seen
|
|
383
|
+
let scriptPreloadLayersPromise = scriptCompiler.getPreloadLayers(scriptFiles)
|
|
384
|
+
let stylePreloadLayersPromise = styleCompiler.getPreloadLayers(styleFiles)
|
|
385
|
+
let preloadLayerGroups = isStyleFilePath(filePaths[0])
|
|
386
|
+
? [stylePreloadLayersPromise, scriptPreloadLayersPromise]
|
|
387
|
+
: [scriptPreloadLayersPromise, stylePreloadLayersPromise]
|
|
388
|
+
|
|
389
|
+
return mergePreloadLayers(await Promise.all(preloadLayerGroups))
|
|
303
390
|
},
|
|
304
391
|
async close() {
|
|
305
392
|
await watcher?.close()
|
|
@@ -323,12 +410,35 @@ function internalServerError(): Response {
|
|
|
323
410
|
})
|
|
324
411
|
}
|
|
325
412
|
|
|
413
|
+
function mergePreloadLayers(preloadLayersByRoot: readonly (readonly string[][])[]): string[] {
|
|
414
|
+
let urls: string[] = []
|
|
415
|
+
let seen = new Set<string>()
|
|
416
|
+
let maxDepth = Math.max(0, ...preloadLayersByRoot.map((layers) => layers.length))
|
|
417
|
+
|
|
418
|
+
for (let depth = 0; depth < maxDepth; depth++) {
|
|
419
|
+
for (let preloadLayers of preloadLayersByRoot) {
|
|
420
|
+
for (let url of preloadLayers[depth] ?? []) {
|
|
421
|
+
if (seen.has(url)) continue
|
|
422
|
+
seen.add(url)
|
|
423
|
+
urls.push(url)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return urls
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function flattenPreloadLayers(preloadLayers: readonly (readonly string[])[]): string[] {
|
|
432
|
+
return preloadLayers.flatMap((layer) => layer)
|
|
433
|
+
}
|
|
434
|
+
|
|
326
435
|
function defaultErrorHandler(error: unknown): void {
|
|
327
436
|
console.error(error)
|
|
328
437
|
}
|
|
329
438
|
|
|
330
439
|
function resolveAssetServerOptions(options: AssetServerOptions): ResolvedAssetServerOptions {
|
|
331
440
|
let rootDir = normalizeFilePath(fs.realpathSync(path.resolve(options.rootDir ?? process.cwd())))
|
|
441
|
+
let basePath = normalizeBasePath(options.basePath)
|
|
332
442
|
let scriptOptions = options.scripts ?? {}
|
|
333
443
|
let fingerprintOptions = normalizeFingerprintOptions({
|
|
334
444
|
fingerprint: options.fingerprint,
|
|
@@ -337,37 +447,36 @@ function resolveAssetServerOptions(options: AssetServerOptions): ResolvedAssetSe
|
|
|
337
447
|
|
|
338
448
|
return {
|
|
339
449
|
allow: options.allow,
|
|
450
|
+
basePath,
|
|
340
451
|
buildId: fingerprintOptions.buildId,
|
|
341
452
|
define: scriptOptions.define,
|
|
342
453
|
deny: options.deny,
|
|
343
454
|
external: scriptOptions.external ?? [],
|
|
344
|
-
|
|
345
|
-
minify:
|
|
455
|
+
fingerprintAssets: fingerprintOptions.enabled,
|
|
456
|
+
minify: options.minify ?? false,
|
|
346
457
|
onError: options.onError ?? defaultErrorHandler,
|
|
347
458
|
rootDir,
|
|
348
|
-
routes: compileRoutes(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
459
|
+
routes: compileRoutes(basePath, [
|
|
460
|
+
{
|
|
461
|
+
fileMap: options.fileMap,
|
|
462
|
+
rootDir,
|
|
463
|
+
},
|
|
464
|
+
...getInjectedPackageRouteConfigs(),
|
|
465
|
+
]),
|
|
466
|
+
sourceMapSourcePaths: options.sourceMapSourcePaths ?? 'url',
|
|
467
|
+
sourceMaps: options.sourceMaps,
|
|
468
|
+
scriptsTarget: resolveScriptTarget(options.target),
|
|
469
|
+
stylesTarget: resolveStyleTarget(options.target),
|
|
355
470
|
watchOptions: normalizeWatchOptions(options.watch),
|
|
356
471
|
}
|
|
357
472
|
}
|
|
358
473
|
|
|
359
|
-
function
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (target == null) return undefined
|
|
363
|
-
|
|
364
|
-
if (typeof target !== 'string' || !scriptTargetSet.has(target)) {
|
|
365
|
-
throw new TypeError(
|
|
366
|
-
`Expected target to be one of ${scriptTargets.map((value) => `"${value}"`).join(', ')}. Received "${target}".`,
|
|
367
|
-
)
|
|
474
|
+
function normalizeBasePath(basePath: string): string {
|
|
475
|
+
if (typeof basePath !== 'string') {
|
|
476
|
+
throw new TypeError('basePath must be a string')
|
|
368
477
|
}
|
|
369
478
|
|
|
370
|
-
return
|
|
479
|
+
return normalizePathname(basePath || '/').replace(/\/+$/, '') || '/'
|
|
371
480
|
}
|
|
372
481
|
|
|
373
482
|
function normalizeFingerprintOptions(options: {
|
|
@@ -413,3 +522,34 @@ function normalizeWatchOptions(
|
|
|
413
522
|
if (options == null || options === true) return {}
|
|
414
523
|
return options
|
|
415
524
|
}
|
|
525
|
+
|
|
526
|
+
function parseAssetRequestPathname(
|
|
527
|
+
pathname: string,
|
|
528
|
+
options: {
|
|
529
|
+
fingerprintAssets: boolean
|
|
530
|
+
routes: CompiledRoutes
|
|
531
|
+
},
|
|
532
|
+
): {
|
|
533
|
+
cacheControl: string
|
|
534
|
+
filePath: string
|
|
535
|
+
isSourceMapRequest: boolean
|
|
536
|
+
requestedFingerprint: string | null
|
|
537
|
+
} | null {
|
|
538
|
+
let isSourceMapRequest = pathname.endsWith('.map')
|
|
539
|
+
let pathWithoutMap = isSourceMapRequest ? pathname.slice(0, -4) : pathname
|
|
540
|
+
let fingerprint = parseFingerprintSuffix(pathWithoutMap)
|
|
541
|
+
let filePath = options.routes.resolveUrlPathname(fingerprint.pathname)
|
|
542
|
+
if (!filePath) return null
|
|
543
|
+
if (options.fingerprintAssets && fingerprint.requestedFingerprint === null) return null
|
|
544
|
+
|
|
545
|
+
return {
|
|
546
|
+
cacheControl: getFingerprintRequestCacheControl(fingerprint.requestedFingerprint),
|
|
547
|
+
filePath,
|
|
548
|
+
isSourceMapRequest,
|
|
549
|
+
requestedFingerprint: fingerprint.requestedFingerprint,
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function isScriptFilePath(filePath: string): boolean {
|
|
554
|
+
return scriptExtensionSet.has(path.extname(filePath).toLowerCase())
|
|
555
|
+
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
type AssetServerCompilationErrorCode =
|
|
2
|
-
| '
|
|
3
|
-
| '
|
|
4
|
-
| '
|
|
5
|
-
| '
|
|
6
|
-
| '
|
|
7
|
-
| '
|
|
2
|
+
| 'FILE_NOT_FOUND'
|
|
3
|
+
| 'FILE_NOT_ALLOWED'
|
|
4
|
+
| 'FILE_OUTSIDE_FILE_MAP'
|
|
5
|
+
| 'COMMONJS_NOT_SUPPORTED'
|
|
6
|
+
| 'TRANSFORM_FAILED'
|
|
7
|
+
| 'EMIT_FAILED'
|
|
8
8
|
| 'IMPORT_RESOLUTION_FAILED'
|
|
9
9
|
| 'IMPORT_NOT_SUPPORTED'
|
|
10
10
|
| 'IMPORT_NOT_ALLOWED'
|
|
11
11
|
| 'IMPORT_OUTSIDE_FILE_MAP'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Internal error used by the request-time
|
|
14
|
+
* Internal error used by the request-time asset compilation pipeline.
|
|
15
15
|
*/
|
|
16
16
|
export class AssetServerCompilationError extends Error {
|
|
17
17
|
code: AssetServerCompilationErrorCode
|
package/src/lib/file-matcher.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from 'node:fs'
|
|
2
|
-
import
|
|
2
|
+
import picomatch from 'picomatch'
|
|
3
3
|
|
|
4
4
|
import { normalizeFilePath, resolveFilePath } from './paths.ts'
|
|
5
5
|
|
|
@@ -37,7 +37,8 @@ export function createFileMatcher(
|
|
|
37
37
|
return (filePath) => filePath === resolvedPatternPath
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
let globMatcher = picomatch(resolvedPatternPath, { dot: true })
|
|
41
|
+
return (filePath) => globMatcher(filePath)
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
function isSameOrDescendantPath(filePath: string, directoryPath: string): boolean {
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
|
|
4
|
+
import { getFilePathDirectory, normalizeFilePath } from './paths.ts'
|
|
5
|
+
|
|
6
|
+
type ResolvedInjectedPackage = {
|
|
7
|
+
packageJsonPath: string
|
|
8
|
+
packageRoot: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const injectedPackageNames = ['@oxc-project/runtime'] as const
|
|
12
|
+
const injectedPackagesBasePath = '/__@remix/injected'
|
|
13
|
+
|
|
14
|
+
const resolvedInjectedPackages = new Map<string, ResolvedInjectedPackage>()
|
|
15
|
+
|
|
16
|
+
export function isInjectedPackageFilePath(filePath: string): boolean {
|
|
17
|
+
let normalizedFilePath = normalizeFilePath(filePath)
|
|
18
|
+
|
|
19
|
+
for (let packageName of injectedPackageNames) {
|
|
20
|
+
let packageRoot = getResolvedInjectedPackage(packageName).packageRoot
|
|
21
|
+
if (normalizedFilePath === packageRoot || normalizedFilePath.startsWith(`${packageRoot}/`)) {
|
|
22
|
+
return true
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getInjectedPackageRouteConfigs(): {
|
|
30
|
+
fileMap: Record<string, string>
|
|
31
|
+
rootDir: string
|
|
32
|
+
}[] {
|
|
33
|
+
return injectedPackageNames.map((packageName) => {
|
|
34
|
+
let { packageRoot } = getResolvedInjectedPackage(packageName)
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
fileMap: {
|
|
38
|
+
[getInjectedPackageRoutePattern(packageName)]: `${packageName}/*path`,
|
|
39
|
+
},
|
|
40
|
+
rootDir: getInjectedPackageRouteRoot(packageRoot, packageName),
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getInjectedPackageNameForSpecifier(specifier: string): string | null {
|
|
46
|
+
for (let packageName of injectedPackageNames) {
|
|
47
|
+
if (specifier === packageName || specifier.startsWith(`${packageName}/`)) {
|
|
48
|
+
return packageName
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function mayContainInjectedPackageSpecifier(sourceText: string): boolean {
|
|
56
|
+
return injectedPackageNames.some((packageName) => sourceText.includes(packageName))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function maskAuthoredInjectedPackageSpecifier(specifier: string): string | null {
|
|
60
|
+
let packageName = getInjectedPackageNameForSpecifier(specifier)
|
|
61
|
+
if (!packageName) return null
|
|
62
|
+
|
|
63
|
+
let maskedPackageName = getMaskedInjectedPackageName(packageName)
|
|
64
|
+
return `${maskedPackageName}${specifier.slice(packageName.length)}`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function restoreAuthoredInjectedPackageSpecifier(specifier: string): string | null {
|
|
68
|
+
for (let packageName of injectedPackageNames) {
|
|
69
|
+
let maskedPackageName = getMaskedInjectedPackageName(packageName)
|
|
70
|
+
if (specifier === maskedPackageName) {
|
|
71
|
+
return packageName
|
|
72
|
+
}
|
|
73
|
+
if (specifier.startsWith(`${maskedPackageName}/`)) {
|
|
74
|
+
return `${packageName}${specifier.slice(maskedPackageName.length)}`
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getMaskedInjectedPackageName(packageName: string): string {
|
|
82
|
+
return `~${packageName.slice(1)}`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function getInjectedPackageImporterPath(): string {
|
|
86
|
+
return normalizeFilePath(fileURLToPath(import.meta.url))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getResolvedInjectedPackage(packageName: string): ResolvedInjectedPackage {
|
|
90
|
+
let existing = resolvedInjectedPackages.get(packageName)
|
|
91
|
+
if (existing) return existing
|
|
92
|
+
|
|
93
|
+
let packageJsonUrl = import.meta.resolve(`${packageName}/package.json`)
|
|
94
|
+
let packageJsonPath = normalizeFilePath(fs.realpathSync(fileURLToPath(packageJsonUrl)))
|
|
95
|
+
|
|
96
|
+
let resolvedInjectedPackage = {
|
|
97
|
+
packageJsonPath,
|
|
98
|
+
packageRoot: normalizeFilePath(fs.realpathSync(getFilePathDirectory(packageJsonPath))),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
resolvedInjectedPackages.set(packageName, resolvedInjectedPackage)
|
|
102
|
+
return resolvedInjectedPackage
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getInjectedPackageRoutePattern(packageName: string): string {
|
|
106
|
+
return `${injectedPackagesBasePath}/${packageName}/*path`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getInjectedPackageRouteRoot(packageRoot: string, packageName: string): string {
|
|
110
|
+
let routeRoot = packageRoot
|
|
111
|
+
|
|
112
|
+
for (let _segment of packageName.split('/')) {
|
|
113
|
+
routeRoot = getFilePathDirectory(routeRoot)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return routeRoot
|
|
117
|
+
}
|