@remix-run/assets 0.1.0 → 0.2.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.
Files changed (57) hide show
  1. package/README.md +53 -47
  2. package/dist/lib/asset-server.d.ts +45 -43
  3. package/dist/lib/asset-server.d.ts.map +1 -1
  4. package/dist/lib/asset-server.js +142 -46
  5. package/dist/lib/compilation-error.d.ts +2 -2
  6. package/dist/lib/compilation-error.d.ts.map +1 -1
  7. package/dist/lib/compilation-error.js +1 -1
  8. package/dist/lib/file-matcher.d.ts.map +1 -1
  9. package/dist/lib/file-matcher.js +3 -2
  10. package/dist/lib/module-store.d.ts +41 -0
  11. package/dist/lib/module-store.d.ts.map +1 -0
  12. package/dist/lib/{scripts/store.js → module-store.js} +43 -41
  13. package/dist/lib/scripts/compiler.d.ts +15 -15
  14. package/dist/lib/scripts/compiler.d.ts.map +1 -1
  15. package/dist/lib/scripts/compiler.js +50 -46
  16. package/dist/lib/scripts/emit.js +2 -2
  17. package/dist/lib/scripts/resolve.d.ts +7 -17
  18. package/dist/lib/scripts/resolve.d.ts.map +1 -1
  19. package/dist/lib/scripts/resolve.js +15 -9
  20. package/dist/lib/scripts/transform.d.ts +11 -9
  21. package/dist/lib/scripts/transform.d.ts.map +1 -1
  22. package/dist/lib/scripts/transform.js +32 -21
  23. package/dist/lib/source-maps.d.ts +1 -1
  24. package/dist/lib/source-maps.d.ts.map +1 -1
  25. package/dist/lib/source-maps.js +7 -1
  26. package/dist/lib/styles/compiler.d.ts +52 -0
  27. package/dist/lib/styles/compiler.d.ts.map +1 -0
  28. package/dist/lib/styles/compiler.js +272 -0
  29. package/dist/lib/styles/emit.d.ts +25 -0
  30. package/dist/lib/styles/emit.d.ts.map +1 -0
  31. package/dist/lib/styles/emit.js +78 -0
  32. package/dist/lib/styles/resolve.d.ts +48 -0
  33. package/dist/lib/styles/resolve.d.ts.map +1 -0
  34. package/dist/lib/styles/resolve.js +188 -0
  35. package/dist/lib/styles/transform.d.ts +47 -0
  36. package/dist/lib/styles/transform.d.ts.map +1 -0
  37. package/dist/lib/styles/transform.js +131 -0
  38. package/dist/lib/target.d.ts +21 -0
  39. package/dist/lib/target.d.ts.map +1 -0
  40. package/dist/lib/target.js +127 -0
  41. package/package.json +7 -2
  42. package/src/lib/asset-server.ts +218 -96
  43. package/src/lib/compilation-error.ts +7 -7
  44. package/src/lib/file-matcher.ts +3 -2
  45. package/src/lib/{scripts/store.ts → module-store.ts} +100 -87
  46. package/src/lib/scripts/compiler.ts +88 -70
  47. package/src/lib/scripts/emit.ts +2 -2
  48. package/src/lib/scripts/resolve.ts +34 -23
  49. package/src/lib/scripts/transform.ts +49 -34
  50. package/src/lib/source-maps.ts +8 -1
  51. package/src/lib/styles/compiler.ts +400 -0
  52. package/src/lib/styles/emit.ts +137 -0
  53. package/src/lib/styles/resolve.ts +316 -0
  54. package/src/lib/styles/transform.ts +226 -0
  55. package/src/lib/target.ts +196 -0
  56. package/dist/lib/scripts/store.d.ts +0 -40
  57. package/dist/lib/scripts/store.d.ts.map +0 -1
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # assets
2
2
 
3
- Fetch-based server for compiling browser JS/TS assets on demand.
3
+ Fetch-based server for compiling browser JS/TS and CSS assets on demand.
4
4
 
5
5
  ## Features
6
6
 
7
- - **On-Demand Compilation** - Compile browser assets on demand
7
+ - **On-Demand Compilation** - Compile browser scripts and styles on demand
8
8
  - **Custom File Mapping** - Define patterns for mapping public URLs to file paths on disk
9
9
  - **Access Control** - Control exactly which files can be served with allow and deny rules
10
- - **Preloads** - Generate modulepreload URLs for `<link>` tags or `Link` headers
10
+ - **Preloads** - Generate preload URLs for scripts and styles based on imports
11
11
  - **Caching** - Conservative caching by default with stable URLs, ETags, and revalidation
12
12
  - **Optional Fingerprinting** - Source-based fingerprinted URLs for long-lived browser caching
13
13
  - **Source Maps** - Serve inline or external sourcemaps
@@ -20,7 +20,7 @@ npm i remix
20
20
 
21
21
  ## Usage
22
22
 
23
- Use `createAssetServer` to serve browser modules from a URL namespace in your app.
23
+ Use `createAssetServer` to serve browser JS/TS and CSS assets from a URL namespace in your app.
24
24
 
25
25
  ```ts
26
26
  import { createRouter } from 'remix/fetch-router'
@@ -41,7 +41,7 @@ router.get('/assets/*', ({ request }) => {
41
41
  })
42
42
  ```
43
43
 
44
- This example gives you an `/assets/*` endpoint that serves compiled browser JS modules from `app/assets` and `node_modules`.
44
+ This example gives you an `/assets/*` endpoint that serves compiled browser assets from `app/assets` and `node_modules`.
45
45
 
46
46
  ## Root Directory
47
47
 
@@ -151,7 +151,7 @@ let src = await assetServer.getHref('app/assets/entry.tsx')
151
151
 
152
152
  ## Preloads
153
153
 
154
- Use `assetServer.getPreloads()` when rendering HTML so you can turn the returned URLs into `<link rel="modulepreload">` tags or `Link` headers for one or more assets and their dependencies. You can provide root-relative or absolute file paths, or `file://` URLs.
154
+ Use `assetServer.getPreloads()` when rendering HTML so you can turn the returned URLs into `<link rel="modulepreload">`, stylesheet preload tags, or `Link` headers for one or more assets and their dependencies. You can provide root-relative or absolute file paths, or `file://` URLs.
155
155
 
156
156
  ```ts
157
157
  let preloads = await assetServer.getPreloads(['app/assets/entry.tsx', 'app/assets/search.tsx'])
@@ -159,16 +159,16 @@ let preloads = await assetServer.getPreloads(['app/assets/entry.tsx', 'app/asset
159
159
  // '/assets/app/assets/entry.tsx',
160
160
  // '/assets/app/assets/search.tsx',
161
161
  // '/assets/app/assets/utils.ts',
162
- // '/assets/npm/@remix-run/component/index.js',
162
+ // '/assets/npm/@remix-run/ui/index.js',
163
163
  // ...etc
164
164
  // ]
165
165
  ```
166
166
 
167
167
  ## Fingerprinting
168
168
 
169
- By default, scripts are served at stable URLs with ETags and `Cache-Control: no-cache`. Responses are cached for the lifetime of the asset server instance.
169
+ By default, assets are served at stable URLs with ETags and `Cache-Control: no-cache`. Responses are cached for the lifetime of the asset server instance.
170
170
 
171
- If you want clients to cache scripts aggressively without revalidation, you can opt into source-based fingerprinting.
171
+ If you want clients to cache assets aggressively without revalidation, you can opt into source-based fingerprinting.
172
172
 
173
173
  ```ts
174
174
  import { createAssetServer } from 'remix/assets'
@@ -183,15 +183,13 @@ let assetServer = createAssetServer({
183
183
  })
184
184
  ```
185
185
 
186
- When fingerprinting is enabled, scripts use a `.@<fingerprint>` segment before the file extension and are served with `Cache-Control: public, max-age=31536000, immutable`.
186
+ When fingerprinting is enabled, assets use a `.@<fingerprint>` segment before the file extension and are served with `Cache-Control: public, max-age=31536000, immutable`.
187
187
 
188
- Source fingerprints are based on the original file contents and the build ID. The build ID must change for each deployment so that fingerprinted modules are invalidated together. This fingerprinting strategy assumes that files on disk won't change, so fingerprinting requires `watch: false`.
188
+ Source fingerprints are based on the original file contents and the build ID. The build ID must change for each deployment so that fingerprinted assets are invalidated together. This fingerprinting strategy assumes that files on disk won't change, so fingerprinting requires `watch: false`.
189
189
 
190
- ## Script Options
191
-
192
- ### Minification
190
+ ## Target
193
191
 
194
- Enable minification with `scripts.minify`:
192
+ Use `target` to lower emitted syntax to a specific browser support policy and/or ECMAScript version.
195
193
 
196
194
  ```ts
197
195
  import { createAssetServer } from 'remix/assets'
@@ -199,15 +197,19 @@ import { createAssetServer } from 'remix/assets'
199
197
  let assetServer = createAssetServer({
200
198
  fileMap: { '/assets/app/*path': 'app/*path' },
201
199
  allow: ['app/assets/**'],
202
- scripts: {
203
- minify: true,
200
+ target: {
201
+ chrome: '109',
202
+ ios: '15.6',
203
+ es: '2020',
204
204
  },
205
205
  })
206
206
  ```
207
207
 
208
- ### Target
208
+ Supported target options are `chrome`, `firefox`, `safari`, `edge`, `opera`, `ios`, `samsung`, and `es` (ECMAScript version).
209
+
210
+ ### Source Maps
209
211
 
210
- Use `scripts.target` to lower emitted syntax to a specific ECMAScript version.
212
+ Enable sourcemaps with either `'external'` or `'inline'` using `sourceMaps`:
211
213
 
212
214
  ```ts
213
215
  import { createAssetServer } from 'remix/assets'
@@ -215,38 +217,26 @@ import { createAssetServer } from 'remix/assets'
215
217
  let assetServer = createAssetServer({
216
218
  fileMap: { '/assets/app/*path': 'app/*path' },
217
219
  allow: ['app/assets/**'],
218
- scripts: {
219
- target: 'es2019',
220
- },
220
+ sourceMaps: 'external',
221
221
  })
222
222
  ```
223
223
 
224
- The default target is `esnext`, which means all syntax is preserved.
225
-
226
- ### Define
227
-
228
- Use `scripts.define` to replace global identifiers with constant expressions.
224
+ By default, sourcemap `sources` use URLs so they're presented alongside the compiled output in your browser's developer tools. You can also use file system paths instead with `sourceMapSourcePaths`:
229
225
 
230
226
  ```ts
231
227
  import { createAssetServer } from 'remix/assets'
232
228
 
233
229
  let assetServer = createAssetServer({
234
230
  fileMap: { '/assets/app/*path': 'app/*path' },
235
- allow: ['app/assets/**', 'app/node_modules/**'],
236
- scripts: {
237
- define: {
238
- 'process.env.NODE_ENV': '"production"',
239
- },
240
- minify: true,
241
- },
231
+ allow: ['app/assets/**'],
232
+ sourceMaps: 'inline',
233
+ sourceMapSourcePaths: 'absolute',
242
234
  })
243
235
  ```
244
236
 
245
- Values are injected exactly as defined, so string literals must include their own quotes, e.g. `process.env.NODE_ENV` must be `"production"` rather than `production`.
246
-
247
- ### Source Maps
237
+ ### Minification
248
238
 
249
- Enable sourcemaps with either `'external'` or `'inline'` using `scripts.sourceMaps`:
239
+ Enable minification with `minify`:
250
240
 
251
241
  ```ts
252
242
  import { createAssetServer } from 'remix/assets'
@@ -254,27 +244,32 @@ import { createAssetServer } from 'remix/assets'
254
244
  let assetServer = createAssetServer({
255
245
  fileMap: { '/assets/app/*path': 'app/*path' },
256
246
  allow: ['app/assets/**'],
257
- scripts: {
258
- sourceMaps: 'external',
259
- },
247
+ minify: true,
260
248
  })
261
249
  ```
262
250
 
263
- By default, sourcemap `sources` use URLs so they're presented alongside the compiled output in your browser's developer tools. You can also use file system paths instead with `scripts.sourceMapSourcePaths`:
251
+ ## Script Options
252
+
253
+ ### Define
254
+
255
+ Use `scripts.define` to replace global identifiers with constant expressions.
264
256
 
265
257
  ```ts
266
258
  import { createAssetServer } from 'remix/assets'
267
259
 
268
260
  let assetServer = createAssetServer({
269
261
  fileMap: { '/assets/app/*path': 'app/*path' },
270
- allow: ['app/assets/**'],
262
+ allow: ['app/assets/**', 'app/node_modules/**'],
271
263
  scripts: {
272
- sourceMaps: 'inline',
273
- sourceMapSourcePaths: 'absolute',
264
+ define: {
265
+ 'process.env.NODE_ENV': '"production"',
266
+ },
274
267
  },
275
268
  })
276
269
  ```
277
270
 
271
+ Values are injected exactly as defined, so string literals must include their own quotes, e.g. `process.env.NODE_ENV` must be `"production"` rather than `production`.
272
+
278
273
  ### External Imports
279
274
 
280
275
  Use `scripts.external` to leave specific import specifiers unchanged by providing an array of specifiers.
@@ -291,6 +286,17 @@ let assetServer = createAssetServer({
291
286
  })
292
287
  ```
293
288
 
289
+ ## CSS Imports
290
+
291
+ Relative CSS `@import` rules are rewritten to asset server URLs. External `@import` URLs are left unchanged automatically. `url()` references are preserved as authored.
292
+
293
+ ```css
294
+ /* Rewritten to asset server URL: */
295
+ @import './reset.css';
296
+ /* External URL: */
297
+ @import 'https://fonts.googleapis.com/css2?family=Inter';
298
+ ```
299
+
294
300
  ## Error Handling
295
301
 
296
302
  Use `onError` to report unexpected compilation failures and/or return a custom response.
@@ -302,8 +308,8 @@ let assetServer = createAssetServer({
302
308
  fileMap: { '/assets/app/*path': 'app/*path' },
303
309
  allow: ['app/assets/**'],
304
310
  onError(error) {
305
- console.error('Failed to build client module', error)
306
- return new Response('Client module build failed', { status: 500 })
311
+ console.error('Failed to build client assets', error)
312
+ return new Response('Client asset build failed', { status: 500 })
307
313
  },
308
314
  })
309
315
  ```
@@ -1,3 +1,4 @@
1
+ import type { AssetTarget } from './target.ts';
1
2
  import type { ChokidarWatcher } from './watch.ts';
2
3
  interface AssetServerWatchOptions {
3
4
  /**
@@ -16,13 +17,22 @@ interface AssetServerWatchOptions {
16
17
  }
17
18
  interface FingerprintOptions {
18
19
  /**
19
- * Per-build invalidation token that must change whenever fingerprinted module URLs
20
+ * Per-build invalidation token that must change whenever fingerprinted asset URLs
20
21
  * should be invalidated together.
21
22
  */
22
23
  buildId: string;
23
24
  }
24
- declare const scriptTargets: readonly ["es2015", "es2016", "es2017", "es2018", "es2019", "es2020", "es2021", "es2022", "es2023", "es2024", "es2025", "es2026", "esnext"];
25
- export type ScriptsTarget = (typeof scriptTargets)[number];
25
+ type AssetSourceMaps = 'inline' | 'external';
26
+ type AssetSourceMapSourcePaths = 'url' | 'absolute';
27
+ interface AssetServerScriptOptions {
28
+ /**
29
+ * Replace global expressions with constant values during transform, e.g.
30
+ * `{ 'process.env.NODE_ENV': '"production"' }`
31
+ */
32
+ define?: Record<string, string>;
33
+ /** Import specifiers to leave unrewritten (CDN URLs, import map entries, etc.) */
34
+ external?: string[];
35
+ }
26
36
  export interface AssetServerOptions {
27
37
  /** File patterns keyed by public URL patterns. */
28
38
  fileMap: Readonly<Record<string, string>>;
@@ -39,45 +49,37 @@ export interface AssetServerOptions {
39
49
  */
40
50
  deny?: readonly string[];
41
51
  /**
42
- * Controls optional source-based URL fingerprinting for rewritten import URLs.
52
+ * Controls optional source-based URL fingerprinting for rewritten asset URLs.
43
53
  *
44
- * When omitted, all served modules use stable non-fingerprinted URLs with `Cache-Control: no-cache`.
54
+ * When omitted, all served assets use stable non-fingerprinted URLs with `Cache-Control: no-cache`.
45
55
  * Cannot be used together with active watch mode. Set `watch: false` when fingerprinting.
46
56
  */
47
57
  fingerprint?: FingerprintOptions;
48
58
  /**
49
- * Script pipeline configuration. Omit to use defaults.
50
- */
51
- scripts?: {
52
- /**
53
- * Source map mode (disabled when omitted).
54
- * - `'external'`: serve source maps as separate `.map` files; adds `//# sourceMappingURL=` comment
55
- * - `'inline'`: embed source maps as a base64 data URL directly in the JS; no separate `.map` file
56
- */
57
- sourceMaps?: 'inline' | 'external';
58
- /**
59
- * Controls the source paths written into source map `sources`.
60
- * - `'url'` (default): use the stable server path (e.g. `'/assets/app/entry.ts'`)
61
- * - `'absolute'`: use the original filesystem path on disk
62
- */
63
- sourceMapSourcePaths?: 'url' | 'absolute';
64
- /**
65
- * Minify emitted modules.
66
- */
67
- minify?: boolean;
68
- /**
69
- * Replace global expressions with constant values during transform, e.g.
70
- * `{ 'process.env.NODE_ENV': '"production"' }`
71
- */
72
- define?: Record<string, string>;
73
- /**
74
- * Lower emitted syntax to a specific ECMAScript target. Omit this option to preserve
75
- * modern syntax unless project configuration already requests a lower target.
76
- */
77
- target?: ScriptsTarget;
78
- /** Import specifiers to leave unrewritten (CDN URLs, import map entries, etc.) */
79
- external?: string[];
80
- };
59
+ * Shared compatibility target for scripts and styles. Browser targets apply to both
60
+ * pipelines, and `es` only affects scripts.
61
+ */
62
+ target?: AssetTarget;
63
+ /**
64
+ * Source map mode for scripts and styles.
65
+ * - `'external'`: serve source maps as separate `.map` files
66
+ * - `'inline'`: embed source maps as a base64 data URL in the compiled asset
67
+ */
68
+ sourceMaps?: AssetSourceMaps;
69
+ /**
70
+ * Source path strategy for source map `sources`.
71
+ * - `'url'` (default): use the stable server path (e.g. `'/assets/app/entry.ts'`)
72
+ * - `'absolute'`: use the original filesystem path on disk
73
+ */
74
+ sourceMapSourcePaths?: AssetSourceMapSourcePaths;
75
+ /**
76
+ * Minification setting for emitted scripts and styles.
77
+ */
78
+ minify?: boolean;
79
+ /**
80
+ * Script-only configuration.
81
+ */
82
+ scripts?: AssetServerScriptOptions;
81
83
  /**
82
84
  * Enable filesystem-backed cache invalidation for long-lived server instances.
83
85
  * Enabled by default. Pass `true` to use the default watcher options, an options
@@ -92,16 +94,16 @@ export interface AssetServerOptions {
92
94
  }
93
95
  export interface AssetServer {
94
96
  /**
95
- * Serves a script request. Returns `Response | null` — null means the request was not
96
- * handled by this server, letting the router fall through to a 404.
97
+ * Serves a script or style request. Returns `Response | null` — null means the request
98
+ * was not handled by this server, letting the router fall through to a 404.
97
99
  */
98
100
  fetch(request: Request): Promise<Response | null>;
99
101
  /**
100
- * Returns the request href for a served module file.
102
+ * Returns the request href for a served asset file.
101
103
  */
102
104
  getHref(filePath: string): Promise<string>;
103
105
  /**
104
- * Returns preload URLs for one or more served module files, ordered shallowest-first.
106
+ * Returns preload URLs for one or more served asset files, ordered shallowest-first.
105
107
  */
106
108
  getPreloads(filePath: string | readonly string[]): Promise<string[]>;
107
109
  /**
@@ -114,8 +116,8 @@ export declare function getInternalWatchTargets(assetServer: AssetServer): reado
114
116
  /**
115
117
  * Create an asset server instance
116
118
  *
117
- * Compiles TypeScript/JavaScript modules on demand with optional source-based URL
118
- * fingerprinting, caching, and configurable file mapping.
119
+ * Compiles TypeScript/JavaScript scripts and CSS styles on demand with optional
120
+ * source-based URL fingerprinting, caching, and configurable file mapping.
119
121
  *
120
122
  * @param options Server configuration
121
123
  * @returns A {@link AssetServer} with `fetch()`, `getHref()`, and `getPreloads()` methods
@@ -1 +1 @@
1
- {"version":3,"file":"asset-server.d.ts","sourceRoot":"","sources":["../../src/lib/asset-server.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAGjD,UAAU,uBAAuB;IAC/B;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1B;;OAEG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IACd;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,UAAU,kBAAkB;IAC1B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,QAAA,MAAM,aAAa,6IAcT,CAAA;AAGV,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AAE1D,MAAM,WAAW,kBAAkB;IACjC,kDAAkD;IAClD,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACzC;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;OAEG;IACH,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,kBAAkB,CAAA;IAChC;;OAEG;IACH,OAAO,CAAC,EAAE;QACR;;;;WAIG;QACH,UAAU,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAA;QAClC;;;;WAIG;QACH,oBAAoB,CAAC,EAAE,KAAK,GAAG,UAAU,CAAA;QACzC;;WAEG;QACH,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB;;;WAGG;QACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC/B;;;WAGG;QACH,MAAM,CAAC,EAAE,aAAa,CAAA;QACtB,kFAAkF;QAClF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KACpB,CAAA;IACD;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,uBAAuB,CAAA;IACzC;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,QAAQ,GAAG,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAA;CACzE;AAED,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;IACjD;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC1C;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IACpE;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAsBD,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,WAAW,GAAG,eAAe,GAAG,SAAS,CAEhG;AAED,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,WAAW,GAAG,SAAS,MAAM,EAAE,CAEnF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CA4H1E"}
1
+ {"version":3,"file":"asset-server.d.ts","sourceRoot":"","sources":["../../src/lib/asset-server.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,WAAW,EAA6C,MAAM,aAAa,CAAA;AAEzF,OAAO,KAAK,EAAsB,eAAe,EAAE,MAAM,YAAY,CAAA;AAErE,UAAU,uBAAuB;IAC/B;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1B;;OAEG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IACd;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,UAAU,kBAAkB;IAC1B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,KAAK,eAAe,GAAG,QAAQ,GAAG,UAAU,CAAA;AAC5C,KAAK,yBAAyB,GAAG,KAAK,GAAG,UAAU,CAAA;AAEnD,UAAU,wBAAwB;IAChC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAID,MAAM,WAAW,kBAAkB;IACjC,kDAAkD;IAClD,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACzC;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;OAEG;IACH,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,kBAAkB,CAAA;IAChC;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,yBAAyB,CAAA;IAChD;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;OAEG;IACH,OAAO,CAAC,EAAE,wBAAwB,CAAA;IAClC;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,uBAAuB,CAAA;IACzC;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,QAAQ,GAAG,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAA;CACzE;AAED,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;IACjD;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC1C;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;IACpE;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAuBD,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,WAAW,GAAG,eAAe,GAAG,SAAS,CAEhG;AAED,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,WAAW,GAAG,SAAS,MAAM,EAAE,CAEnF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAqN1E"}
@@ -1,27 +1,16 @@
1
1
  import * as path from 'node:path';
2
2
  import * as fs from 'node:fs';
3
- import { isAssetServerCompilationError } from "./compilation-error.js";
4
3
  import { createAccessPolicy } from "./access.js";
5
- import { createModuleCompiler, createResponseForModule } from "./scripts/compiler.js";
4
+ import { isAssetServerCompilationError } from "./compilation-error.js";
5
+ import { getFingerprintRequestCacheControl, parseFingerprintSuffix } from "./fingerprint.js";
6
6
  import { normalizeFilePath } from "./paths.js";
7
7
  import { compileRoutes } from "./routes.js";
8
+ import { createResponseForScript, createScriptCompiler } from "./scripts/compiler.js";
9
+ import { supportedScriptExtensions } from "./scripts/resolve.js";
10
+ import { createResponseForStyle, createStyleCompiler, isStyleFilePath } from "./styles/compiler.js";
11
+ import { resolveScriptTarget, resolveStyleTarget } from "./target.js";
8
12
  import { createAssetServerWatcher } from "./watch.js";
9
- const scriptTargets = [
10
- 'es2015',
11
- 'es2016',
12
- 'es2017',
13
- 'es2018',
14
- 'es2019',
15
- 'es2020',
16
- 'es2021',
17
- 'es2022',
18
- 'es2023',
19
- 'es2024',
20
- 'es2025',
21
- 'es2026',
22
- 'esnext',
23
- ];
24
- const scriptTargetSet = new Set(scriptTargets);
13
+ const scriptExtensionSet = new Set(supportedScriptExtensions);
25
14
  const chokidarWatcherByAssetServer = new WeakMap();
26
15
  const watcherByAssetServer = new WeakMap();
27
16
  export function getInternalChokidarWatcher(assetServer) {
@@ -33,8 +22,8 @@ export function getInternalWatchTargets(assetServer) {
33
22
  /**
34
23
  * Create an asset server instance
35
24
  *
36
- * Compiles TypeScript/JavaScript modules on demand with optional source-based URL
37
- * fingerprinting, caching, and configurable file mapping.
25
+ * Compiles TypeScript/JavaScript scripts and CSS styles on demand with optional
26
+ * source-based URL fingerprinting, caching, and configurable file mapping.
38
27
  *
39
28
  * @param options Server configuration
40
29
  * @returns A {@link AssetServer} with `fetch()`, `getHref()`, and `getPreloads()` methods
@@ -60,11 +49,11 @@ export function createAssetServer(options) {
60
49
  });
61
50
  let watcher = null;
62
51
  let chokidarWatcher = null;
63
- let moduleCompiler = createModuleCompiler({
52
+ let scriptCompiler = createScriptCompiler({
64
53
  buildId: resolvedOptions.buildId,
65
54
  define: resolvedOptions.define,
66
55
  external: resolvedOptions.external,
67
- fingerprintModules: resolvedOptions.fingerprintModules,
56
+ fingerprintAssets: resolvedOptions.fingerprintAssets,
68
57
  isAllowed: accessPolicy.isAllowed,
69
58
  minify: resolvedOptions.minify,
70
59
  onWatchDirectoriesChange: (delta) => {
@@ -80,6 +69,23 @@ export function createAssetServer(options) {
80
69
  watchIgnore: resolvedOptions.watchOptions?.ignore,
81
70
  watchMode: resolvedOptions.watchOptions !== null,
82
71
  });
72
+ let styleCompiler = createStyleCompiler({
73
+ buildId: resolvedOptions.buildId,
74
+ fingerprintAssets: resolvedOptions.fingerprintAssets,
75
+ isAllowed: accessPolicy.isAllowed,
76
+ minify: resolvedOptions.minify,
77
+ onWatchDirectoriesChange: (delta) => {
78
+ if (!watcher)
79
+ return;
80
+ watcher.updateWatchedDirectories(delta);
81
+ },
82
+ rootDir: resolvedOptions.rootDir,
83
+ routes: resolvedOptions.routes,
84
+ sourceMapSourcePaths: resolvedOptions.sourceMapSourcePaths,
85
+ sourceMaps: resolvedOptions.sourceMaps,
86
+ targets: resolvedOptions.stylesTarget,
87
+ watchIgnore: resolvedOptions.watchOptions?.ignore,
88
+ });
83
89
  if (resolvedOptions.watchOptions) {
84
90
  watcher = createAssetServerWatcher({
85
91
  ...resolvedOptions.watchOptions,
@@ -102,7 +108,8 @@ export function createAssetServer(options) {
102
108
  async function handleWatchEvent(filePath, event) {
103
109
  try {
104
110
  let normalizedFilePath = normalizeFilePath(filePath);
105
- await moduleCompiler.handleFileEvent(normalizedFilePath, event);
111
+ await scriptCompiler.handleFileEvent(normalizedFilePath, event);
112
+ await styleCompiler.handleFileEvent(normalizedFilePath, event);
106
113
  }
107
114
  catch (error) {
108
115
  console.error(`There was an error invalidating the asset server cache: ${error}`);
@@ -112,28 +119,58 @@ export function createAssetServer(options) {
112
119
  async fetch(request) {
113
120
  if (request.method !== 'GET' && request.method !== 'HEAD')
114
121
  return null;
115
- let parsedRequestPathname = moduleCompiler.parseRequestPathname(new URL(request.url).pathname);
122
+ let parsedRequestPathname = parseAssetRequestPathname(new URL(request.url).pathname, {
123
+ fingerprintAssets: resolvedOptions.fingerprintAssets,
124
+ routes: resolvedOptions.routes,
125
+ });
116
126
  if (!parsedRequestPathname)
117
127
  return null;
118
128
  try {
119
129
  let ifNoneMatch = request.headers.get('If-None-Match');
120
- let moduleResult = await moduleCompiler.getModule(parsedRequestPathname.filePath, {
130
+ if (isStyleFilePath(parsedRequestPathname.filePath)) {
131
+ let styleResult = await styleCompiler.getStyle(parsedRequestPathname.filePath, {
132
+ ifNoneMatch,
133
+ isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
134
+ requestedFingerprint: parsedRequestPathname.requestedFingerprint,
135
+ });
136
+ if (styleResult.type === 'not-modified') {
137
+ return new Response(null, {
138
+ status: 304,
139
+ headers: { ETag: styleResult.etag },
140
+ });
141
+ }
142
+ let compiledStyle = styleResult.style;
143
+ if (parsedRequestPathname.requestedFingerprint !== null) {
144
+ if (compiledStyle.fingerprint !== parsedRequestPathname.requestedFingerprint)
145
+ return null;
146
+ }
147
+ return createResponseForStyle(compiledStyle, {
148
+ cacheControl: parsedRequestPathname.cacheControl,
149
+ ifNoneMatch,
150
+ isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
151
+ method: request.method,
152
+ });
153
+ }
154
+ if (!isScriptFilePath(parsedRequestPathname.filePath)) {
155
+ return null;
156
+ }
157
+ let scriptResult = await scriptCompiler.getScript(parsedRequestPathname.filePath, {
121
158
  ifNoneMatch,
122
159
  isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
123
160
  requestedFingerprint: parsedRequestPathname.requestedFingerprint,
124
161
  });
125
- if (moduleResult.type === 'not-modified') {
162
+ if (scriptResult.type === 'not-modified') {
126
163
  return new Response(null, {
127
164
  status: 304,
128
- headers: { ETag: moduleResult.etag },
165
+ headers: { ETag: scriptResult.etag },
129
166
  });
130
167
  }
131
- let compiledModule = moduleResult.module;
168
+ let compiledScript = scriptResult.script;
132
169
  if (parsedRequestPathname.requestedFingerprint !== null) {
133
- if (compiledModule.fingerprint !== parsedRequestPathname.requestedFingerprint)
170
+ if (compiledScript.fingerprint !== parsedRequestPathname.requestedFingerprint)
134
171
  return null;
135
172
  }
136
- return createResponseForModule(compiledModule, {
173
+ return createResponseForScript(compiledScript, {
137
174
  cacheControl: parsedRequestPathname.cacheControl,
138
175
  ifNoneMatch,
139
176
  isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
@@ -145,17 +182,45 @@ export function createAssetServer(options) {
145
182
  // compiling imports. In this fetch context, both cases should fall through as "not
146
183
  // handled here" so the outer router can continue to its own 404 behavior.
147
184
  if (isAssetServerCompilationError(error) &&
148
- (error.code === 'MODULE_NOT_FOUND' || error.code === 'MODULE_NOT_ALLOWED')) {
185
+ (error.code === 'FILE_NOT_FOUND' || error.code === 'FILE_NOT_ALLOWED')) {
149
186
  return null;
150
187
  }
151
188
  return responseForError(error);
152
189
  }
153
190
  },
154
191
  async getHref(filePath) {
155
- return moduleCompiler.getHref(filePath);
192
+ if (isStyleFilePath(filePath)) {
193
+ return styleCompiler.getHref(filePath);
194
+ }
195
+ return scriptCompiler.getHref(filePath);
156
196
  },
157
197
  async getPreloads(filePath) {
158
- return moduleCompiler.getPreloadUrls(filePath);
198
+ let filePaths = Array.isArray(filePath) ? filePath : [filePath];
199
+ let styleFiles = [];
200
+ let scriptFiles = [];
201
+ for (let nextFilePath of filePaths) {
202
+ if (isStyleFilePath(nextFilePath)) {
203
+ styleFiles.push(nextFilePath);
204
+ continue;
205
+ }
206
+ scriptFiles.push(nextFilePath);
207
+ }
208
+ if (styleFiles.length === 0 && scriptFiles.length === 0) {
209
+ return [];
210
+ }
211
+ if (styleFiles.length === 0) {
212
+ return flattenPreloadLayers(await scriptCompiler.getPreloadLayers(filePath));
213
+ }
214
+ if (scriptFiles.length === 0) {
215
+ return flattenPreloadLayers(await styleCompiler.getPreloadLayers(filePath));
216
+ }
217
+ // Mixed asset type preloads need to be merged, so we merge in order of first asset type seen
218
+ let scriptPreloadLayersPromise = scriptCompiler.getPreloadLayers(scriptFiles);
219
+ let stylePreloadLayersPromise = styleCompiler.getPreloadLayers(styleFiles);
220
+ let preloadLayerGroups = isStyleFilePath(filePaths[0])
221
+ ? [stylePreloadLayersPromise, scriptPreloadLayersPromise]
222
+ : [scriptPreloadLayersPromise, stylePreloadLayersPromise];
223
+ return mergePreloadLayers(await Promise.all(preloadLayerGroups));
159
224
  },
160
225
  async close() {
161
226
  await watcher?.close();
@@ -175,6 +240,25 @@ function internalServerError() {
175
240
  headers: { 'Content-Type': 'text/plain; charset=utf-8' },
176
241
  });
177
242
  }
243
+ function mergePreloadLayers(preloadLayersByRoot) {
244
+ let urls = [];
245
+ let seen = new Set();
246
+ let maxDepth = Math.max(0, ...preloadLayersByRoot.map((layers) => layers.length));
247
+ for (let depth = 0; depth < maxDepth; depth++) {
248
+ for (let preloadLayers of preloadLayersByRoot) {
249
+ for (let url of preloadLayers[depth] ?? []) {
250
+ if (seen.has(url))
251
+ continue;
252
+ seen.add(url);
253
+ urls.push(url);
254
+ }
255
+ }
256
+ }
257
+ return urls;
258
+ }
259
+ function flattenPreloadLayers(preloadLayers) {
260
+ return preloadLayers.flatMap((layer) => layer);
261
+ }
178
262
  function defaultErrorHandler(error) {
179
263
  console.error(error);
180
264
  }
@@ -191,28 +275,21 @@ function resolveAssetServerOptions(options) {
191
275
  define: scriptOptions.define,
192
276
  deny: options.deny,
193
277
  external: scriptOptions.external ?? [],
194
- fingerprintModules: fingerprintOptions.enabled,
195
- minify: scriptOptions.minify ?? false,
278
+ fingerprintAssets: fingerprintOptions.enabled,
279
+ minify: options.minify ?? false,
196
280
  onError: options.onError ?? defaultErrorHandler,
197
281
  rootDir,
198
282
  routes: compileRoutes({
199
283
  fileMap: options.fileMap,
200
284
  rootDir,
201
285
  }),
202
- sourceMapSourcePaths: scriptOptions.sourceMapSourcePaths ?? 'url',
203
- sourceMaps: scriptOptions.sourceMaps,
204
- scriptsTarget: normalizeTarget(scriptOptions.target),
286
+ sourceMapSourcePaths: options.sourceMapSourcePaths ?? 'url',
287
+ sourceMaps: options.sourceMaps,
288
+ scriptsTarget: resolveScriptTarget(options.target),
289
+ stylesTarget: resolveStyleTarget(options.target),
205
290
  watchOptions: normalizeWatchOptions(options.watch),
206
291
  };
207
292
  }
208
- function normalizeTarget(target) {
209
- if (target == null)
210
- return undefined;
211
- if (typeof target !== 'string' || !scriptTargetSet.has(target)) {
212
- throw new TypeError(`Expected target to be one of ${scriptTargets.map((value) => `"${value}"`).join(', ')}. Received "${target}".`);
213
- }
214
- return target;
215
- }
216
293
  function normalizeFingerprintOptions(options) {
217
294
  if (!options.fingerprint) {
218
295
  return {
@@ -240,3 +317,22 @@ function normalizeWatchOptions(options) {
240
317
  return {};
241
318
  return options;
242
319
  }
320
+ function parseAssetRequestPathname(pathname, options) {
321
+ let isSourceMapRequest = pathname.endsWith('.map');
322
+ let pathWithoutMap = isSourceMapRequest ? pathname.slice(0, -4) : pathname;
323
+ let fingerprint = parseFingerprintSuffix(pathWithoutMap);
324
+ let filePath = options.routes.resolveUrlPathname(fingerprint.pathname);
325
+ if (!filePath)
326
+ return null;
327
+ if (options.fingerprintAssets && fingerprint.requestedFingerprint === null)
328
+ return null;
329
+ return {
330
+ cacheControl: getFingerprintRequestCacheControl(fingerprint.requestedFingerprint),
331
+ filePath,
332
+ isSourceMapRequest,
333
+ requestedFingerprint: fingerprint.requestedFingerprint,
334
+ };
335
+ }
336
+ function isScriptFilePath(filePath) {
337
+ return scriptExtensionSet.has(path.extname(filePath).toLowerCase());
338
+ }