@remix-run/assets 0.0.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.
- package/LICENSE +21 -0
- package/README.md +325 -2
- package/dist/assets.d.ts +3 -0
- package/dist/assets.d.ts.map +1 -0
- package/dist/assets.js +1 -0
- package/dist/lib/access.d.ts +10 -0
- package/dist/lib/access.d.ts.map +1 -0
- package/dist/lib/access.js +14 -0
- package/dist/lib/asset-server.d.ts +139 -0
- package/dist/lib/asset-server.d.ts.map +1 -0
- package/dist/lib/asset-server.js +338 -0
- package/dist/lib/compilation-error.d.ts +33 -0
- package/dist/lib/compilation-error.d.ts.map +1 -0
- package/dist/lib/compilation-error.js +32 -0
- package/dist/lib/file-matcher.d.ts +6 -0
- package/dist/lib/file-matcher.d.ts.map +1 -0
- package/dist/lib/file-matcher.js +44 -0
- package/dist/lib/fingerprint.d.ts +12 -0
- package/dist/lib/fingerprint.d.ts.map +1 -0
- package/dist/lib/fingerprint.js +49 -0
- package/dist/lib/module-store.d.ts +41 -0
- package/dist/lib/module-store.d.ts.map +1 -0
- package/dist/lib/module-store.js +230 -0
- package/dist/lib/paths.d.ts +8 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +50 -0
- package/dist/lib/routes.d.ts +13 -0
- package/dist/lib/routes.d.ts.map +1 -0
- package/dist/lib/routes.js +94 -0
- package/dist/lib/scripts/cjs-check.d.ts +3 -0
- package/dist/lib/scripts/cjs-check.d.ts.map +1 -0
- package/dist/lib/scripts/cjs-check.js +398 -0
- package/dist/lib/scripts/compiler.d.ts +62 -0
- package/dist/lib/scripts/compiler.d.ts.map +1 -0
- package/dist/lib/scripts/compiler.js +439 -0
- package/dist/lib/scripts/emit.d.ts +25 -0
- package/dist/lib/scripts/emit.d.ts.map +1 -0
- package/dist/lib/scripts/emit.js +63 -0
- package/dist/lib/scripts/resolve.d.ts +50 -0
- package/dist/lib/scripts/resolve.d.ts.map +1 -0
- package/dist/lib/scripts/resolve.js +236 -0
- package/dist/lib/scripts/transform.d.ts +64 -0
- package/dist/lib/scripts/transform.d.ts.map +1 -0
- package/dist/lib/scripts/transform.js +373 -0
- package/dist/lib/source-maps.d.ts +4 -0
- package/dist/lib/source-maps.d.ts.map +1 -0
- package/dist/lib/source-maps.js +62 -0
- 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/dist/lib/watch.d.ts +22 -0
- package/dist/lib/watch.d.ts.map +1 -0
- package/dist/lib/watch.js +96 -0
- package/package.json +55 -12
- package/src/assets.ts +2 -0
- package/src/lib/access.ts +24 -0
- package/src/lib/asset-server.ts +537 -0
- package/src/lib/compilation-error.ts +61 -0
- package/src/lib/file-matcher.ts +63 -0
- package/src/lib/fingerprint.ts +65 -0
- package/src/lib/module-store.ts +340 -0
- package/src/lib/paths.ts +66 -0
- package/src/lib/routes.ts +164 -0
- package/src/lib/scripts/cjs-check.ts +476 -0
- package/src/lib/scripts/compiler.ts +640 -0
- package/src/lib/scripts/emit.ts +122 -0
- package/src/lib/scripts/resolve.ts +433 -0
- package/src/lib/scripts/transform.ts +609 -0
- package/src/lib/source-maps.ts +75 -0
- 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/src/lib/watch.ts +136 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import { createAccessPolicy } from "./access.js";
|
|
4
|
+
import { isAssetServerCompilationError } from "./compilation-error.js";
|
|
5
|
+
import { getFingerprintRequestCacheControl, parseFingerprintSuffix } from "./fingerprint.js";
|
|
6
|
+
import { normalizeFilePath } from "./paths.js";
|
|
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";
|
|
12
|
+
import { createAssetServerWatcher } from "./watch.js";
|
|
13
|
+
const scriptExtensionSet = new Set(supportedScriptExtensions);
|
|
14
|
+
const chokidarWatcherByAssetServer = new WeakMap();
|
|
15
|
+
const watcherByAssetServer = new WeakMap();
|
|
16
|
+
export function getInternalChokidarWatcher(assetServer) {
|
|
17
|
+
return chokidarWatcherByAssetServer.get(assetServer);
|
|
18
|
+
}
|
|
19
|
+
export function getInternalWatchTargets(assetServer) {
|
|
20
|
+
return watcherByAssetServer.get(assetServer)?.getWatchedTargets() ?? [];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Create an asset server instance
|
|
24
|
+
*
|
|
25
|
+
* Compiles TypeScript/JavaScript scripts and CSS styles on demand with optional
|
|
26
|
+
* source-based URL fingerprinting, caching, and configurable file mapping.
|
|
27
|
+
*
|
|
28
|
+
* @param options Server configuration
|
|
29
|
+
* @returns A {@link AssetServer} with `fetch()`, `getHref()`, and `getPreloads()` methods
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* let assetServer = createAssetServer({
|
|
34
|
+
* fileMap: {
|
|
35
|
+
* '/assets/app/*path': 'app/*path',
|
|
36
|
+
* },
|
|
37
|
+
* allow: ['app/**'],
|
|
38
|
+
* })
|
|
39
|
+
*
|
|
40
|
+
* route('/assets/*path', ({ request }) => assetServer.fetch(request))
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export function createAssetServer(options) {
|
|
44
|
+
let resolvedOptions = resolveAssetServerOptions(options);
|
|
45
|
+
let accessPolicy = createAccessPolicy({
|
|
46
|
+
allow: resolvedOptions.allow,
|
|
47
|
+
deny: resolvedOptions.deny,
|
|
48
|
+
rootDir: resolvedOptions.rootDir,
|
|
49
|
+
});
|
|
50
|
+
let watcher = null;
|
|
51
|
+
let chokidarWatcher = null;
|
|
52
|
+
let scriptCompiler = createScriptCompiler({
|
|
53
|
+
buildId: resolvedOptions.buildId,
|
|
54
|
+
define: resolvedOptions.define,
|
|
55
|
+
external: resolvedOptions.external,
|
|
56
|
+
fingerprintAssets: resolvedOptions.fingerprintAssets,
|
|
57
|
+
isAllowed: accessPolicy.isAllowed,
|
|
58
|
+
minify: resolvedOptions.minify,
|
|
59
|
+
onWatchDirectoriesChange: (delta) => {
|
|
60
|
+
if (!watcher)
|
|
61
|
+
return;
|
|
62
|
+
watcher.updateWatchedDirectories(delta);
|
|
63
|
+
},
|
|
64
|
+
rootDir: resolvedOptions.rootDir,
|
|
65
|
+
routes: resolvedOptions.routes,
|
|
66
|
+
sourceMapSourcePaths: resolvedOptions.sourceMapSourcePaths,
|
|
67
|
+
sourceMaps: resolvedOptions.sourceMaps,
|
|
68
|
+
target: resolvedOptions.scriptsTarget,
|
|
69
|
+
watchIgnore: resolvedOptions.watchOptions?.ignore,
|
|
70
|
+
watchMode: resolvedOptions.watchOptions !== null,
|
|
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
|
+
});
|
|
89
|
+
if (resolvedOptions.watchOptions) {
|
|
90
|
+
watcher = createAssetServerWatcher({
|
|
91
|
+
...resolvedOptions.watchOptions,
|
|
92
|
+
onChokidarWatcherCreated(createdWatcher) {
|
|
93
|
+
chokidarWatcher = createdWatcher;
|
|
94
|
+
},
|
|
95
|
+
onFileEvent: handleWatchEvent,
|
|
96
|
+
rootDir: resolvedOptions.rootDir,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
async function responseForError(error) {
|
|
100
|
+
try {
|
|
101
|
+
return (await resolvedOptions.onError(error)) ?? internalServerError();
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error(`There was an error in the asset server error handler: ${error}`);
|
|
105
|
+
return internalServerError();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function handleWatchEvent(filePath, event) {
|
|
109
|
+
try {
|
|
110
|
+
let normalizedFilePath = normalizeFilePath(filePath);
|
|
111
|
+
await scriptCompiler.handleFileEvent(normalizedFilePath, event);
|
|
112
|
+
await styleCompiler.handleFileEvent(normalizedFilePath, event);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error(`There was an error invalidating the asset server cache: ${error}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
let assetServer = {
|
|
119
|
+
async fetch(request) {
|
|
120
|
+
if (request.method !== 'GET' && request.method !== 'HEAD')
|
|
121
|
+
return null;
|
|
122
|
+
let parsedRequestPathname = parseAssetRequestPathname(new URL(request.url).pathname, {
|
|
123
|
+
fingerprintAssets: resolvedOptions.fingerprintAssets,
|
|
124
|
+
routes: resolvedOptions.routes,
|
|
125
|
+
});
|
|
126
|
+
if (!parsedRequestPathname)
|
|
127
|
+
return null;
|
|
128
|
+
try {
|
|
129
|
+
let ifNoneMatch = request.headers.get('If-None-Match');
|
|
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, {
|
|
158
|
+
ifNoneMatch,
|
|
159
|
+
isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
|
|
160
|
+
requestedFingerprint: parsedRequestPathname.requestedFingerprint,
|
|
161
|
+
});
|
|
162
|
+
if (scriptResult.type === 'not-modified') {
|
|
163
|
+
return new Response(null, {
|
|
164
|
+
status: 304,
|
|
165
|
+
headers: { ETag: scriptResult.etag },
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
let compiledScript = scriptResult.script;
|
|
169
|
+
if (parsedRequestPathname.requestedFingerprint !== null) {
|
|
170
|
+
if (compiledScript.fingerprint !== parsedRequestPathname.requestedFingerprint)
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return createResponseForScript(compiledScript, {
|
|
174
|
+
cacheControl: parsedRequestPathname.cacheControl,
|
|
175
|
+
ifNoneMatch,
|
|
176
|
+
isSourceMapRequest: parsedRequestPathname.isSourceMapRequest,
|
|
177
|
+
method: request.method,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
// A direct request can race with the filesystem or fail a deeper allow check while
|
|
182
|
+
// compiling imports. In this fetch context, both cases should fall through as "not
|
|
183
|
+
// handled here" so the outer router can continue to its own 404 behavior.
|
|
184
|
+
if (isAssetServerCompilationError(error) &&
|
|
185
|
+
(error.code === 'FILE_NOT_FOUND' || error.code === 'FILE_NOT_ALLOWED')) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
return responseForError(error);
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
async getHref(filePath) {
|
|
192
|
+
if (isStyleFilePath(filePath)) {
|
|
193
|
+
return styleCompiler.getHref(filePath);
|
|
194
|
+
}
|
|
195
|
+
return scriptCompiler.getHref(filePath);
|
|
196
|
+
},
|
|
197
|
+
async getPreloads(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));
|
|
224
|
+
},
|
|
225
|
+
async close() {
|
|
226
|
+
await watcher?.close();
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
if (chokidarWatcher) {
|
|
230
|
+
chokidarWatcherByAssetServer.set(assetServer, chokidarWatcher);
|
|
231
|
+
}
|
|
232
|
+
if (watcher) {
|
|
233
|
+
watcherByAssetServer.set(assetServer, watcher);
|
|
234
|
+
}
|
|
235
|
+
return assetServer;
|
|
236
|
+
}
|
|
237
|
+
function internalServerError() {
|
|
238
|
+
return new Response('Internal Server Error', {
|
|
239
|
+
status: 500,
|
|
240
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
241
|
+
});
|
|
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
|
+
}
|
|
262
|
+
function defaultErrorHandler(error) {
|
|
263
|
+
console.error(error);
|
|
264
|
+
}
|
|
265
|
+
function resolveAssetServerOptions(options) {
|
|
266
|
+
let rootDir = normalizeFilePath(fs.realpathSync(path.resolve(options.rootDir ?? process.cwd())));
|
|
267
|
+
let scriptOptions = options.scripts ?? {};
|
|
268
|
+
let fingerprintOptions = normalizeFingerprintOptions({
|
|
269
|
+
fingerprint: options.fingerprint,
|
|
270
|
+
watch: options.watch,
|
|
271
|
+
});
|
|
272
|
+
return {
|
|
273
|
+
allow: options.allow,
|
|
274
|
+
buildId: fingerprintOptions.buildId,
|
|
275
|
+
define: scriptOptions.define,
|
|
276
|
+
deny: options.deny,
|
|
277
|
+
external: scriptOptions.external ?? [],
|
|
278
|
+
fingerprintAssets: fingerprintOptions.enabled,
|
|
279
|
+
minify: options.minify ?? false,
|
|
280
|
+
onError: options.onError ?? defaultErrorHandler,
|
|
281
|
+
rootDir,
|
|
282
|
+
routes: compileRoutes({
|
|
283
|
+
fileMap: options.fileMap,
|
|
284
|
+
rootDir,
|
|
285
|
+
}),
|
|
286
|
+
sourceMapSourcePaths: options.sourceMapSourcePaths ?? 'url',
|
|
287
|
+
sourceMaps: options.sourceMaps,
|
|
288
|
+
scriptsTarget: resolveScriptTarget(options.target),
|
|
289
|
+
stylesTarget: resolveStyleTarget(options.target),
|
|
290
|
+
watchOptions: normalizeWatchOptions(options.watch),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function normalizeFingerprintOptions(options) {
|
|
294
|
+
if (!options.fingerprint) {
|
|
295
|
+
return {
|
|
296
|
+
enabled: false,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
if (typeof options.fingerprint.buildId !== 'string') {
|
|
300
|
+
throw new TypeError('fingerprint.buildId must be a string');
|
|
301
|
+
}
|
|
302
|
+
if (options.fingerprint.buildId.length === 0) {
|
|
303
|
+
throw new TypeError('fingerprint.buildId must be a non-empty string');
|
|
304
|
+
}
|
|
305
|
+
if (options.watch !== false) {
|
|
306
|
+
throw new TypeError('fingerprint cannot be used with watch mode');
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
enabled: true,
|
|
310
|
+
buildId: options.fingerprint.buildId,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function normalizeWatchOptions(options) {
|
|
314
|
+
if (options === false)
|
|
315
|
+
return null;
|
|
316
|
+
if (options == null || options === true)
|
|
317
|
+
return {};
|
|
318
|
+
return options;
|
|
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
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
type AssetServerCompilationErrorCode = 'FILE_NOT_FOUND' | 'FILE_NOT_ALLOWED' | 'FILE_OUTSIDE_FILE_MAP' | 'COMMONJS_NOT_SUPPORTED' | 'TRANSFORM_FAILED' | 'EMIT_FAILED' | 'IMPORT_RESOLUTION_FAILED' | 'IMPORT_NOT_SUPPORTED' | 'IMPORT_NOT_ALLOWED' | 'IMPORT_OUTSIDE_FILE_MAP';
|
|
2
|
+
/**
|
|
3
|
+
* Internal error used by the request-time asset compilation pipeline.
|
|
4
|
+
*/
|
|
5
|
+
export declare class AssetServerCompilationError extends Error {
|
|
6
|
+
code: AssetServerCompilationErrorCode;
|
|
7
|
+
constructor(message: string, options: {
|
|
8
|
+
cause?: unknown;
|
|
9
|
+
code: AssetServerCompilationErrorCode;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Returns true when a value is an `AssetServerCompilationError`.
|
|
14
|
+
*
|
|
15
|
+
* @param error Value thrown by the compilation pipeline.
|
|
16
|
+
* @returns Whether the value is an `AssetServerCompilationError`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isAssetServerCompilationError(error: unknown): error is AssetServerCompilationError;
|
|
19
|
+
/**
|
|
20
|
+
* Creates an `AssetServerCompilationError` with a stable internal code.
|
|
21
|
+
*
|
|
22
|
+
* @param message Human-readable error message.
|
|
23
|
+
* @param options Structured internal error details.
|
|
24
|
+
* @param options.cause Original error cause, when available.
|
|
25
|
+
* @param options.code Stable internal compilation error code.
|
|
26
|
+
* @returns A `AssetServerCompilationError`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createAssetServerCompilationError(message: string, options: {
|
|
29
|
+
cause?: unknown;
|
|
30
|
+
code: AssetServerCompilationErrorCode;
|
|
31
|
+
}): AssetServerCompilationError;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=compilation-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compilation-error.d.ts","sourceRoot":"","sources":["../../src/lib/compilation-error.ts"],"names":[],"mappings":"AAAA,KAAK,+BAA+B,GAChC,gBAAgB,GAChB,kBAAkB,GAClB,uBAAuB,GACvB,wBAAwB,GACxB,kBAAkB,GAClB,aAAa,GACb,0BAA0B,GAC1B,sBAAsB,GACtB,oBAAoB,GACpB,yBAAyB,CAAA;AAE7B;;GAEG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,IAAI,EAAE,+BAA+B,CAAA;IAErC,YACE,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QACP,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,IAAI,EAAE,+BAA+B,CAAA;KACtC,EAKF;CACF;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,2BAA2B,CAEtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IACP,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,IAAI,EAAE,+BAA+B,CAAA;CACtC,GACA,2BAA2B,CAE7B"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal error used by the request-time asset compilation pipeline.
|
|
3
|
+
*/
|
|
4
|
+
export class AssetServerCompilationError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(message, options) {
|
|
7
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
8
|
+
this.name = 'AssetServerCompilationError';
|
|
9
|
+
this.code = options.code;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Returns true when a value is an `AssetServerCompilationError`.
|
|
14
|
+
*
|
|
15
|
+
* @param error Value thrown by the compilation pipeline.
|
|
16
|
+
* @returns Whether the value is an `AssetServerCompilationError`.
|
|
17
|
+
*/
|
|
18
|
+
export function isAssetServerCompilationError(error) {
|
|
19
|
+
return error instanceof AssetServerCompilationError;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Creates an `AssetServerCompilationError` with a stable internal code.
|
|
23
|
+
*
|
|
24
|
+
* @param message Human-readable error message.
|
|
25
|
+
* @param options Structured internal error details.
|
|
26
|
+
* @param options.cause Original error cause, when available.
|
|
27
|
+
* @param options.code Stable internal compilation error code.
|
|
28
|
+
* @returns A `AssetServerCompilationError`.
|
|
29
|
+
*/
|
|
30
|
+
export function createAssetServerCompilationError(message, options) {
|
|
31
|
+
return new AssetServerCompilationError(message, options);
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-matcher.d.ts","sourceRoot":"","sources":["../../src/lib/file-matcher.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAA;AAEvD,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IACP,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAA;CAClB,GACL,WAAW,CA2Bb"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import picomatch from 'picomatch';
|
|
3
|
+
import { normalizeFilePath, resolveFilePath } from "./paths.js";
|
|
4
|
+
export function createFileMatcher(pattern, rootDir, options = {}) {
|
|
5
|
+
let resolvedPatternPath = resolveFilePath(rootDir, pattern);
|
|
6
|
+
let allowDirectories = options.allowDirectories ?? true;
|
|
7
|
+
let allowMissing = options.allowMissing ?? true;
|
|
8
|
+
if (!containsGlobSyntax(pattern)) {
|
|
9
|
+
try {
|
|
10
|
+
resolvedPatternPath = normalizeFilePath(fs.realpathSync(resolvedPatternPath));
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (!allowMissing || !isPathNotFoundError(error))
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
if (allowDirectories) {
|
|
17
|
+
try {
|
|
18
|
+
if (fs.statSync(resolveFilePath(rootDir, pattern)).isDirectory()) {
|
|
19
|
+
return (filePath) => isSameOrDescendantPath(filePath, resolvedPatternPath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (!isPathNotFoundError(error))
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return (filePath) => filePath === resolvedPatternPath;
|
|
28
|
+
}
|
|
29
|
+
let globMatcher = picomatch(resolvedPatternPath, { dot: true });
|
|
30
|
+
return (filePath) => globMatcher(filePath);
|
|
31
|
+
}
|
|
32
|
+
function isSameOrDescendantPath(filePath, directoryPath) {
|
|
33
|
+
let normalizedDirectoryPath = directoryPath.replace(/\/+$/, '');
|
|
34
|
+
return filePath === normalizedDirectoryPath || filePath.startsWith(`${normalizedDirectoryPath}/`);
|
|
35
|
+
}
|
|
36
|
+
function containsGlobSyntax(pattern) {
|
|
37
|
+
return /[*?[\]{}()!+@]/.test(pattern);
|
|
38
|
+
}
|
|
39
|
+
function isPathNotFoundError(error) {
|
|
40
|
+
return (error instanceof Error &&
|
|
41
|
+
'code' in error &&
|
|
42
|
+
(error.code === 'ENOENT' ||
|
|
43
|
+
error.code === 'ENOTDIR'));
|
|
44
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function hashContent(content: string): Promise<string>;
|
|
2
|
+
export declare function generateFingerprint(options: {
|
|
3
|
+
buildId: string;
|
|
4
|
+
content: string;
|
|
5
|
+
}): Promise<string>;
|
|
6
|
+
export declare function parseFingerprintSuffix(pathname: string): {
|
|
7
|
+
pathname: string;
|
|
8
|
+
requestedFingerprint: string | null;
|
|
9
|
+
};
|
|
10
|
+
export declare function formatFingerprintedPathname(pathname: string, fingerprint: string | null): string;
|
|
11
|
+
export declare function getFingerprintRequestCacheControl(requestedFingerprint: string | null): string;
|
|
12
|
+
//# sourceMappingURL=fingerprint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../../src/lib/fingerprint.ts"],"names":[],"mappings":"AAGA,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKlE;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAElB;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG;IACxD,QAAQ,EAAE,MAAM,CAAA;IAChB,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAA;CACpC,CAyBA;AAED,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAahG;AAED,wBAAgB,iCAAiC,CAAC,oBAAoB,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAE7F"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const fingerprintedExtensionRE = /^(.+)\.@([A-Za-z0-9_-]+)(\.[^./]+)$/;
|
|
2
|
+
const fingerprintedBasenameRE = /^(.+)\.@([A-Za-z0-9_-]+)$/;
|
|
3
|
+
export async function hashContent(content) {
|
|
4
|
+
let encoder = new TextEncoder();
|
|
5
|
+
let data = encoder.encode(content);
|
|
6
|
+
let hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
7
|
+
return Buffer.from(hashBuffer).toString('base64url').slice(0, 6);
|
|
8
|
+
}
|
|
9
|
+
export async function generateFingerprint(options) {
|
|
10
|
+
return hashContent(JSON.stringify([options.content, options.buildId]));
|
|
11
|
+
}
|
|
12
|
+
export function parseFingerprintSuffix(pathname) {
|
|
13
|
+
let lastSlashIndex = pathname.lastIndexOf('/');
|
|
14
|
+
let directory = lastSlashIndex >= 0 ? pathname.slice(0, lastSlashIndex + 1) : '';
|
|
15
|
+
let basename = lastSlashIndex >= 0 ? pathname.slice(lastSlashIndex + 1) : pathname;
|
|
16
|
+
let extensionMatch = basename.match(fingerprintedExtensionRE);
|
|
17
|
+
if (extensionMatch) {
|
|
18
|
+
return {
|
|
19
|
+
pathname: `${directory}${extensionMatch[1]}${extensionMatch[3]}`,
|
|
20
|
+
requestedFingerprint: extensionMatch[2],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
let basenameMatch = basename.match(fingerprintedBasenameRE);
|
|
24
|
+
if (basenameMatch) {
|
|
25
|
+
return {
|
|
26
|
+
pathname: `${directory}${basenameMatch[1]}`,
|
|
27
|
+
requestedFingerprint: basenameMatch[2],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
pathname,
|
|
32
|
+
requestedFingerprint: null,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function formatFingerprintedPathname(pathname, fingerprint) {
|
|
36
|
+
if (fingerprint === null)
|
|
37
|
+
return pathname;
|
|
38
|
+
let lastSlashIndex = pathname.lastIndexOf('/');
|
|
39
|
+
let directory = lastSlashIndex >= 0 ? pathname.slice(0, lastSlashIndex + 1) : '';
|
|
40
|
+
let basename = lastSlashIndex >= 0 ? pathname.slice(lastSlashIndex + 1) : pathname;
|
|
41
|
+
let lastDotIndex = basename.lastIndexOf('.');
|
|
42
|
+
if (lastDotIndex <= 0) {
|
|
43
|
+
return `${pathname}.@${fingerprint}`;
|
|
44
|
+
}
|
|
45
|
+
return `${directory}${basename.slice(0, lastDotIndex)}.@${fingerprint}${basename.slice(lastDotIndex)}`;
|
|
46
|
+
}
|
|
47
|
+
export function getFingerprintRequestCacheControl(requestedFingerprint) {
|
|
48
|
+
return requestedFingerprint === null ? 'no-cache' : 'public, max-age=31536000, immutable';
|
|
49
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type ModuleTracking = {
|
|
2
|
+
trackedFiles: readonly string[];
|
|
3
|
+
trackedDirectories?: readonly string[];
|
|
4
|
+
};
|
|
5
|
+
export type ModuleWatchEvent = 'change' | 'add' | 'unlink';
|
|
6
|
+
export type FileSnapshot = {
|
|
7
|
+
mtimeNs: bigint;
|
|
8
|
+
size: bigint;
|
|
9
|
+
};
|
|
10
|
+
export type ModuleSnapshot = ReadonlyMap<string, FileSnapshot>;
|
|
11
|
+
type ModuleRecordState<transformed, resolved, emitted> = {
|
|
12
|
+
identityPath: string;
|
|
13
|
+
invalidationVersion: number;
|
|
14
|
+
transformed?: transformed;
|
|
15
|
+
resolved?: resolved;
|
|
16
|
+
emitted?: emitted;
|
|
17
|
+
emittedSnapshot?: ModuleSnapshot;
|
|
18
|
+
staleEmitted?: emitted;
|
|
19
|
+
staleEmittedSnapshot?: ModuleSnapshot;
|
|
20
|
+
trackedFiles: ReadonlySet<string>;
|
|
21
|
+
trackedDirectories: ReadonlySet<string>;
|
|
22
|
+
};
|
|
23
|
+
export type ModuleRecord<transformed, resolved, emitted> = Readonly<ModuleRecordState<transformed, resolved, emitted>>;
|
|
24
|
+
export type ModuleStore<transformed, resolved, emitted> = {
|
|
25
|
+
get(identityPath: string): ModuleRecord<transformed, resolved, emitted>;
|
|
26
|
+
clearTransformed(identityPath: string, tracking: readonly ModuleTracking[]): void;
|
|
27
|
+
setTransformed(identityPath: string, transformed: transformed, tracking: readonly ModuleTracking[]): void;
|
|
28
|
+
setResolved(identityPath: string, resolved: resolved, tracking: readonly ModuleTracking[]): void;
|
|
29
|
+
clearResolved(identityPath: string, tracking: readonly ModuleTracking[]): void;
|
|
30
|
+
setEmitted(identityPath: string, emitted: emitted, snapshot: ModuleSnapshot | null): void;
|
|
31
|
+
invalidateForFileEvent(filePath: string, event: ModuleWatchEvent): void;
|
|
32
|
+
invalidateAll(): void;
|
|
33
|
+
};
|
|
34
|
+
export declare function createModuleStore<transformed, resolved, emitted>(options?: {
|
|
35
|
+
onWatchDirectoriesChange?: (delta: {
|
|
36
|
+
add: string[];
|
|
37
|
+
remove: string[];
|
|
38
|
+
}) => void;
|
|
39
|
+
}): ModuleStore<transformed, resolved, emitted>;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=module-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module-store.d.ts","sourceRoot":"","sources":["../../src/lib/module-store.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAC3B,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IAC/B,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,KAAK,GAAG,QAAQ,CAAA;AAE1D,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;AAE9D,KAAK,iBAAiB,CAAC,WAAW,EAAE,QAAQ,EAAE,OAAO,IAAI;IACvD,YAAY,EAAE,MAAM,CAAA;IACpB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,eAAe,CAAC,EAAE,cAAc,CAAA;IAChC,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,oBAAoB,CAAC,EAAE,cAAc,CAAA;IACrC,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACjC,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CACxC,CAAA;AAED,MAAM,MAAM,YAAY,CAAC,WAAW,EAAE,QAAQ,EAAE,OAAO,IAAI,QAAQ,CACjE,iBAAiB,CAAC,WAAW,EAAE,QAAQ,EAAE,OAAO,CAAC,CAClD,CAAA;AAeD,MAAM,MAAM,WAAW,CAAC,WAAW,EAAE,QAAQ,EAAE,OAAO,IAAI;IACxD,GAAG,CAAC,YAAY,EAAE,MAAM,GAAG,YAAY,CAAC,WAAW,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAA;IACvE,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,IAAI,CAAA;IACjF,cAAc,CACZ,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,SAAS,cAAc,EAAE,GAClC,IAAI,CAAA;IACP,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,IAAI,CAAA;IAChG,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,IAAI,CAAA;IAC9E,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,GAAG,IAAI,CAAA;IACzF,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACvE,aAAa,IAAI,IAAI,CAAA;CACtB,CAAA;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,QAAQ,EAAE,OAAO,EAC9D,OAAO,GAAE;IACP,wBAAwB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,KAAK,IAAI,CAAA;CAC3E,GACL,WAAW,CAAC,WAAW,EAAE,QAAQ,EAAE,OAAO,CAAC,CA4M7C"}
|