@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.
- package/README.md +53 -47
- package/dist/lib/asset-server.d.ts +45 -43
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +142 -46
- 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/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/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 +15 -9
- 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 +32 -21
- 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 +7 -2
- package/src/lib/asset-server.ts +218 -96
- package/src/lib/compilation-error.ts +7 -7
- package/src/lib/file-matcher.ts +3 -2
- package/src/lib/{scripts/store.ts → module-store.ts} +100 -87
- package/src/lib/scripts/compiler.ts +88 -70
- package/src/lib/scripts/emit.ts +2 -2
- package/src/lib/scripts/resolve.ts +34 -23
- package/src/lib/scripts/transform.ts +49 -34
- 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
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import * as os from 'node:os';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { IfNoneMatch } from '@remix-run/headers';
|
|
5
|
+
import { createFileMatcher } from "../file-matcher.js";
|
|
6
|
+
import { formatFingerprintedPathname } from "../fingerprint.js";
|
|
7
|
+
import { createModuleStore } from "../module-store.js";
|
|
8
|
+
import { normalizeFilePath, resolveFilePath } from "../paths.js";
|
|
9
|
+
import { emitResolvedStyle } from "./emit.js";
|
|
10
|
+
import { resolveServedStyleOrThrow, resolveStyle } from "./resolve.js";
|
|
11
|
+
import { transformStyle } from "./transform.js";
|
|
12
|
+
const preloadConcurrency = Math.max(1, Math.min(8, os.availableParallelism() - 1));
|
|
13
|
+
const styleExtension = '.css';
|
|
14
|
+
export function createStyleCompiler(options) {
|
|
15
|
+
let resolvedOptions = {
|
|
16
|
+
...options,
|
|
17
|
+
watchIgnoreMatchers: (options.watchIgnore ?? []).map((pattern) => createFileMatcher(pattern, options.rootDir)),
|
|
18
|
+
};
|
|
19
|
+
let styleStore = createModuleStore({
|
|
20
|
+
onWatchDirectoriesChange: options.onWatchDirectoriesChange,
|
|
21
|
+
});
|
|
22
|
+
let resolveInFlightByCacheKey = new Map();
|
|
23
|
+
let emitInFlightByCacheKey = new Map();
|
|
24
|
+
let resolveArgs = {
|
|
25
|
+
isAllowed: resolvedOptions.isAllowed,
|
|
26
|
+
isWatchIgnored,
|
|
27
|
+
routes: resolvedOptions.routes,
|
|
28
|
+
};
|
|
29
|
+
let transformArgs = {
|
|
30
|
+
buildId: resolvedOptions.buildId ?? null,
|
|
31
|
+
isWatchIgnored,
|
|
32
|
+
minify: resolvedOptions.minify,
|
|
33
|
+
routes: resolvedOptions.routes,
|
|
34
|
+
sourceMapSourcePaths: resolvedOptions.sourceMapSourcePaths,
|
|
35
|
+
sourceMaps: resolvedOptions.sourceMaps ?? null,
|
|
36
|
+
targets: resolvedOptions.targets ?? null,
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
async getHref(filePath) {
|
|
40
|
+
let resolvedStyle = resolveServedStyleOrThrow(resolveInputFilePath(filePath), resolveArgs);
|
|
41
|
+
return getServedUrl(resolvedStyle.identityPath);
|
|
42
|
+
},
|
|
43
|
+
async getPreloadLayers(filePath) {
|
|
44
|
+
let resolvedEntries = [];
|
|
45
|
+
let seen = new Set();
|
|
46
|
+
for (let resolvedStyle of (Array.isArray(filePath) ? filePath : [filePath]).map((nextPath) => resolveServedStyleOrThrow(resolveInputFilePath(nextPath), resolveArgs))) {
|
|
47
|
+
if (seen.has(resolvedStyle.identityPath))
|
|
48
|
+
continue;
|
|
49
|
+
seen.add(resolvedStyle.identityPath);
|
|
50
|
+
resolvedEntries.push(resolvedStyle.identityPath);
|
|
51
|
+
}
|
|
52
|
+
let visited = new Set(resolvedEntries);
|
|
53
|
+
let queue = [...resolvedEntries];
|
|
54
|
+
let layers = [];
|
|
55
|
+
while (queue.length > 0) {
|
|
56
|
+
let frontier = queue;
|
|
57
|
+
queue = [];
|
|
58
|
+
let resolvedStyles = await getOrCreateResolvedStyles(frontier.map((identityPath) => styleStore.get(identityPath)));
|
|
59
|
+
let layer = [];
|
|
60
|
+
for (let resolvedStyle of resolvedStyles) {
|
|
61
|
+
layer.push(getServedUrlForResolvedStyle(resolvedStyle));
|
|
62
|
+
for (let dep of resolvedStyle.deps) {
|
|
63
|
+
if (visited.has(dep))
|
|
64
|
+
continue;
|
|
65
|
+
visited.add(dep);
|
|
66
|
+
queue.push(dep);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
layers.push(layer);
|
|
70
|
+
}
|
|
71
|
+
return layers;
|
|
72
|
+
},
|
|
73
|
+
async getStyle(filePath, getOptions) {
|
|
74
|
+
let resolvedStyle = resolveServedStyleOrThrow(resolveInputFilePath(filePath), resolveArgs);
|
|
75
|
+
let record = styleStore.get(resolvedStyle.identityPath);
|
|
76
|
+
let notModified = getNotModifiedStyle(record.emitted, getOptions);
|
|
77
|
+
if (notModified)
|
|
78
|
+
return notModified;
|
|
79
|
+
let emitted = await getOrCreateEmittedStyle(record);
|
|
80
|
+
return {
|
|
81
|
+
style: toStyleCompileResult(emitted),
|
|
82
|
+
type: 'style',
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
async handleFileEvent(filePath, event) {
|
|
86
|
+
let normalizedFilePath = normalizeFilePath(filePath);
|
|
87
|
+
if (isWatchIgnored(normalizedFilePath))
|
|
88
|
+
return;
|
|
89
|
+
styleStore.invalidateForFileEvent(normalizedFilePath, event);
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
function resolveInputFilePath(filePath) {
|
|
93
|
+
if (filePath.startsWith('file://')) {
|
|
94
|
+
return normalizeFilePath(fileURLToPath(new URL(filePath)));
|
|
95
|
+
}
|
|
96
|
+
if (filePath.includes('://')) {
|
|
97
|
+
throw new TypeError(`Expected a file path or file:// URL, received "${filePath}"`);
|
|
98
|
+
}
|
|
99
|
+
return resolveFilePath(resolvedOptions.rootDir, filePath);
|
|
100
|
+
}
|
|
101
|
+
async function getOrCreateResolvedStyles(records) {
|
|
102
|
+
return mapWithConcurrency(records, preloadConcurrency, (record) => getOrCreateResolvedStyle(record));
|
|
103
|
+
}
|
|
104
|
+
async function getOrCreateResolvedStyle(record) {
|
|
105
|
+
if (record.resolved)
|
|
106
|
+
return record.resolved;
|
|
107
|
+
let cacheKey = getRecordCacheKey(record);
|
|
108
|
+
let existing = resolveInFlightByCacheKey.get(cacheKey);
|
|
109
|
+
if (existing)
|
|
110
|
+
return existing;
|
|
111
|
+
let promise = (async () => {
|
|
112
|
+
let startedVersion = record.invalidationVersion;
|
|
113
|
+
let transformedStyle = await getOrCreateTransformedStyle(record);
|
|
114
|
+
let resolvedStyleResult = await resolveStyle(record, transformedStyle, resolveArgs);
|
|
115
|
+
if (!resolvedStyleResult.ok) {
|
|
116
|
+
if (isFresh(record, startedVersion)) {
|
|
117
|
+
styleStore.clearResolved(record.identityPath, [resolvedStyleResult.tracking]);
|
|
118
|
+
}
|
|
119
|
+
throw resolvedStyleResult.error;
|
|
120
|
+
}
|
|
121
|
+
if (isFresh(record, startedVersion)) {
|
|
122
|
+
styleStore.setResolved(record.identityPath, resolvedStyleResult.value, [
|
|
123
|
+
resolvedStyleResult.tracking,
|
|
124
|
+
]);
|
|
125
|
+
}
|
|
126
|
+
return resolvedStyleResult.value;
|
|
127
|
+
})();
|
|
128
|
+
resolveInFlightByCacheKey.set(cacheKey, promise);
|
|
129
|
+
try {
|
|
130
|
+
return await promise;
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
if (resolveInFlightByCacheKey.get(cacheKey) === promise) {
|
|
134
|
+
resolveInFlightByCacheKey.delete(cacheKey);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function getOrCreateTransformedStyle(record) {
|
|
139
|
+
if (record.transformed)
|
|
140
|
+
return record.transformed;
|
|
141
|
+
let startedVersion = record.invalidationVersion;
|
|
142
|
+
let transformStyleResult = await transformStyle(record, transformArgs);
|
|
143
|
+
if (!transformStyleResult.ok) {
|
|
144
|
+
if (isFresh(record, startedVersion)) {
|
|
145
|
+
styleStore.clearTransformed(record.identityPath, [transformStyleResult.tracking]);
|
|
146
|
+
}
|
|
147
|
+
throw transformStyleResult.error;
|
|
148
|
+
}
|
|
149
|
+
if (isFresh(record, startedVersion)) {
|
|
150
|
+
styleStore.setTransformed(record.identityPath, transformStyleResult.value, [
|
|
151
|
+
transformStyleResult.tracking,
|
|
152
|
+
]);
|
|
153
|
+
}
|
|
154
|
+
return transformStyleResult.value;
|
|
155
|
+
}
|
|
156
|
+
async function getOrCreateEmittedStyle(record) {
|
|
157
|
+
if (record.emitted)
|
|
158
|
+
return record.emitted;
|
|
159
|
+
let cacheKey = getRecordCacheKey(record);
|
|
160
|
+
let existing = emitInFlightByCacheKey.get(cacheKey);
|
|
161
|
+
if (existing)
|
|
162
|
+
return existing;
|
|
163
|
+
let promise = (async () => {
|
|
164
|
+
let startedVersion = record.invalidationVersion;
|
|
165
|
+
let resolvedStyle = await getOrCreateResolvedStyle(record);
|
|
166
|
+
let emitResolvedStyleResult = await emitResolvedStyle(resolvedStyle, {
|
|
167
|
+
getServedUrl,
|
|
168
|
+
sourceMaps: resolvedOptions.sourceMaps,
|
|
169
|
+
});
|
|
170
|
+
if (!emitResolvedStyleResult.ok) {
|
|
171
|
+
throw emitResolvedStyleResult.error;
|
|
172
|
+
}
|
|
173
|
+
if (isFresh(record, startedVersion)) {
|
|
174
|
+
styleStore.setEmitted(record.identityPath, emitResolvedStyleResult.value, null);
|
|
175
|
+
}
|
|
176
|
+
return emitResolvedStyleResult.value;
|
|
177
|
+
})();
|
|
178
|
+
emitInFlightByCacheKey.set(cacheKey, promise);
|
|
179
|
+
try {
|
|
180
|
+
return await promise;
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
if (emitInFlightByCacheKey.get(cacheKey) === promise) {
|
|
184
|
+
emitInFlightByCacheKey.delete(cacheKey);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async function getServedUrl(identityPath) {
|
|
189
|
+
return getServedUrlForResolvedStyle(await getOrCreateResolvedStyle(styleStore.get(identityPath)));
|
|
190
|
+
}
|
|
191
|
+
function getServedUrlForResolvedStyle(resolvedStyle) {
|
|
192
|
+
return formatFingerprintedPathname(resolvedStyle.stableUrlPathname, resolvedOptions.fingerprintAssets ? resolvedStyle.fingerprint : null);
|
|
193
|
+
}
|
|
194
|
+
function isWatchIgnored(filePath) {
|
|
195
|
+
return resolvedOptions.watchIgnoreMatchers.some((matcher) => matcher(filePath));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function getRecordCacheKey(record) {
|
|
199
|
+
return `${record.identityPath}\0${record.invalidationVersion}`;
|
|
200
|
+
}
|
|
201
|
+
function isFresh(record, version) {
|
|
202
|
+
return record.invalidationVersion === version;
|
|
203
|
+
}
|
|
204
|
+
function getNotModifiedStyle(emittedStyle, options) {
|
|
205
|
+
if (!emittedStyle || options.ifNoneMatch === null)
|
|
206
|
+
return null;
|
|
207
|
+
if (options.requestedFingerprint !== null &&
|
|
208
|
+
emittedStyle.fingerprint !== options.requestedFingerprint) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
let asset = getEmittedAssetForRequest(emittedStyle, options.isSourceMapRequest);
|
|
212
|
+
if (!asset)
|
|
213
|
+
return null;
|
|
214
|
+
if (!IfNoneMatch.from(options.ifNoneMatch).matches(asset.etag))
|
|
215
|
+
return null;
|
|
216
|
+
return { etag: asset.etag, type: 'not-modified' };
|
|
217
|
+
}
|
|
218
|
+
function getEmittedAssetForRequest(emittedStyle, isSourceMapRequest) {
|
|
219
|
+
return isSourceMapRequest ? emittedStyle.sourceMap : emittedStyle.code;
|
|
220
|
+
}
|
|
221
|
+
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
222
|
+
if (items.length === 0)
|
|
223
|
+
return [];
|
|
224
|
+
let results = new Array(items.length);
|
|
225
|
+
let nextIndex = 0;
|
|
226
|
+
async function worker() {
|
|
227
|
+
while (nextIndex < items.length) {
|
|
228
|
+
let index = nextIndex++;
|
|
229
|
+
results[index] = await mapper(items[index], index);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
|
|
233
|
+
return results;
|
|
234
|
+
}
|
|
235
|
+
function toStyleCompileResult(emittedStyle) {
|
|
236
|
+
return {
|
|
237
|
+
code: emittedStyle.code,
|
|
238
|
+
fingerprint: emittedStyle.fingerprint,
|
|
239
|
+
sourceMap: emittedStyle.sourceMap,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
export function isStyleFilePath(filePath) {
|
|
243
|
+
return path.extname(filePath).toLowerCase() === styleExtension;
|
|
244
|
+
}
|
|
245
|
+
export function createResponseForStyle(result, options) {
|
|
246
|
+
let body;
|
|
247
|
+
let etag;
|
|
248
|
+
let contentType;
|
|
249
|
+
if (options.isSourceMapRequest) {
|
|
250
|
+
if (!result.sourceMap) {
|
|
251
|
+
return new Response('Not found', { status: 404 });
|
|
252
|
+
}
|
|
253
|
+
body = options.method === 'HEAD' ? null : result.sourceMap.content;
|
|
254
|
+
etag = result.sourceMap.etag;
|
|
255
|
+
contentType = 'application/json; charset=utf-8';
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
body = options.method === 'HEAD' ? null : result.code.content;
|
|
259
|
+
etag = result.code.etag;
|
|
260
|
+
contentType = 'text/css; charset=utf-8';
|
|
261
|
+
}
|
|
262
|
+
if (IfNoneMatch.from(options.ifNoneMatch).matches(etag)) {
|
|
263
|
+
return new Response(null, { status: 304, headers: { ETag: etag } });
|
|
264
|
+
}
|
|
265
|
+
return new Response(body, {
|
|
266
|
+
headers: {
|
|
267
|
+
'Cache-Control': options.cacheControl,
|
|
268
|
+
'Content-Type': contentType,
|
|
269
|
+
ETag: etag,
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AssetServerCompilationError } from '../compilation-error.ts';
|
|
2
|
+
import type { ResolvedStyle } from './resolve.ts';
|
|
3
|
+
export type EmittedAsset = {
|
|
4
|
+
content: string;
|
|
5
|
+
etag: string;
|
|
6
|
+
};
|
|
7
|
+
export type EmittedStyle = {
|
|
8
|
+
code: EmittedAsset;
|
|
9
|
+
fingerprint: string | null;
|
|
10
|
+
importUrls: string[];
|
|
11
|
+
sourceMap: EmittedAsset | null;
|
|
12
|
+
};
|
|
13
|
+
type EmitResult = {
|
|
14
|
+
ok: true;
|
|
15
|
+
value: EmittedStyle;
|
|
16
|
+
} | {
|
|
17
|
+
error: AssetServerCompilationError;
|
|
18
|
+
ok: false;
|
|
19
|
+
};
|
|
20
|
+
export declare function emitResolvedStyle(resolvedStyle: ResolvedStyle, options: {
|
|
21
|
+
getServedUrl(identityPath: string): Promise<string>;
|
|
22
|
+
sourceMaps?: 'external' | 'inline';
|
|
23
|
+
}): Promise<EmitResult>;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=emit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../../src/lib/styles/emit.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAA;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAEjD,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,YAAY,CAAA;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,SAAS,EAAE,YAAY,GAAG,IAAI,CAAA;CAC/B,CAAA;AAED,KAAK,UAAU,GACX;IACE,EAAE,EAAE,IAAI,CAAA;IACR,KAAK,EAAE,YAAY,CAAA;CACpB,GACD;IACE,KAAK,EAAE,2BAA2B,CAAA;IAClC,EAAE,EAAE,KAAK,CAAA;CACV,CAAA;AAEL,wBAAsB,iBAAiB,CACrC,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE;IACP,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACnD,UAAU,CAAC,EAAE,UAAU,GAAG,QAAQ,CAAA;CACnC,GACA,OAAO,CAAC,UAAU,CAAC,CAkCrB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import MagicString from 'magic-string';
|
|
2
|
+
import { createAssetServerCompilationError, isAssetServerCompilationError, } from "../compilation-error.js";
|
|
3
|
+
import { hashContent } from "../fingerprint.js";
|
|
4
|
+
import { composeSourceMaps } from "../source-maps.js";
|
|
5
|
+
export async function emitResolvedStyle(resolvedStyle, options) {
|
|
6
|
+
try {
|
|
7
|
+
let importUrls = await Promise.all(resolvedStyle.deps.map((depPath) => options.getServedUrl(depPath)));
|
|
8
|
+
let rewriteResult = await rewriteDependencies(resolvedStyle, options);
|
|
9
|
+
let finalCode = rewriteResult.code;
|
|
10
|
+
if (rewriteResult.sourceMap) {
|
|
11
|
+
if (options.sourceMaps === 'inline') {
|
|
12
|
+
let encoded = Buffer.from(rewriteResult.sourceMap).toString('base64');
|
|
13
|
+
finalCode += `\n/*# sourceMappingURL=data:application/json;base64,${encoded} */`;
|
|
14
|
+
}
|
|
15
|
+
else if (options.sourceMaps === 'external') {
|
|
16
|
+
finalCode += `\n/*# sourceMappingURL=${await options.getServedUrl(resolvedStyle.identityPath)}.map */`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
ok: true,
|
|
21
|
+
value: {
|
|
22
|
+
code: await createEmittedAsset(finalCode),
|
|
23
|
+
fingerprint: resolvedStyle.fingerprint,
|
|
24
|
+
importUrls,
|
|
25
|
+
sourceMap: rewriteResult.sourceMap
|
|
26
|
+
? await createEmittedAsset(rewriteResult.sourceMap)
|
|
27
|
+
: null,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
error: toEmitError(error, resolvedStyle.identityPath),
|
|
34
|
+
ok: false,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function rewriteDependencies(resolvedStyle, options) {
|
|
39
|
+
if (resolvedStyle.dependencies.length === 0) {
|
|
40
|
+
return {
|
|
41
|
+
code: resolvedStyle.rawCode,
|
|
42
|
+
sourceMap: resolvedStyle.sourceMap,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
let rewrittenSource = new MagicString(resolvedStyle.rawCode);
|
|
46
|
+
for (let dependency of resolvedStyle.dependencies) {
|
|
47
|
+
let replacement = dependency.kind === 'external'
|
|
48
|
+
? dependency.replacement
|
|
49
|
+
: `${await options.getServedUrl(dependency.depPath)}${dependency.suffix}`;
|
|
50
|
+
let start = resolvedStyle.rawCode.indexOf(dependency.placeholder);
|
|
51
|
+
if (start < 0) {
|
|
52
|
+
throw createAssetServerCompilationError(`Missing dependency placeholder "${dependency.placeholder}" while emitting style ${resolvedStyle.identityPath}.`, {
|
|
53
|
+
code: 'EMIT_FAILED',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
rewrittenSource.overwrite(start, start + dependency.placeholder.length, replacement);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
code: rewrittenSource.toString(),
|
|
60
|
+
sourceMap: resolvedStyle.sourceMap
|
|
61
|
+
? composeSourceMaps(rewrittenSource.generateMap({ hires: true }).toString(), resolvedStyle.sourceMap)
|
|
62
|
+
: null,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async function createEmittedAsset(content) {
|
|
66
|
+
return {
|
|
67
|
+
content,
|
|
68
|
+
etag: `W/"${await hashContent(content)}"`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function toEmitError(error, identityPath) {
|
|
72
|
+
if (isAssetServerCompilationError(error))
|
|
73
|
+
return error;
|
|
74
|
+
return createAssetServerCompilationError(`Failed to emit style ${identityPath}. ${error instanceof Error ? error.message : String(error)}`, {
|
|
75
|
+
cause: error,
|
|
76
|
+
code: 'EMIT_FAILED',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { AssetServerCompilationError } from '../compilation-error.ts';
|
|
2
|
+
import type { ModuleRecord, ModuleTracking } from '../module-store.ts';
|
|
3
|
+
import type { CompiledRoutes } from '../routes.ts';
|
|
4
|
+
import type { EmittedStyle } from './emit.ts';
|
|
5
|
+
import type { TransformedStyle } from './transform.ts';
|
|
6
|
+
type StyleRecord = ModuleRecord<TransformedStyle, ResolvedStyle, EmittedStyle>;
|
|
7
|
+
type ResolvedDependency = {
|
|
8
|
+
kind: 'external';
|
|
9
|
+
placeholder: string;
|
|
10
|
+
replacement: string;
|
|
11
|
+
} | {
|
|
12
|
+
depPath: string;
|
|
13
|
+
kind: 'local';
|
|
14
|
+
placeholder: string;
|
|
15
|
+
suffix: string;
|
|
16
|
+
};
|
|
17
|
+
export type ResolvedStyle = {
|
|
18
|
+
dependencies: ResolvedDependency[];
|
|
19
|
+
deps: string[];
|
|
20
|
+
fingerprint: string | null;
|
|
21
|
+
identityPath: string;
|
|
22
|
+
rawCode: string;
|
|
23
|
+
resolvedPath: string;
|
|
24
|
+
sourceMap: string | null;
|
|
25
|
+
stableUrlPathname: string;
|
|
26
|
+
trackedFiles: string[];
|
|
27
|
+
};
|
|
28
|
+
export type ResolveArgs = {
|
|
29
|
+
isAllowed(absolutePath: string): boolean;
|
|
30
|
+
isWatchIgnored(filePath: string): boolean;
|
|
31
|
+
routes: CompiledRoutes;
|
|
32
|
+
};
|
|
33
|
+
type ResolveResult = {
|
|
34
|
+
tracking: ModuleTracking;
|
|
35
|
+
} & ({
|
|
36
|
+
ok: true;
|
|
37
|
+
value: ResolvedStyle;
|
|
38
|
+
} | {
|
|
39
|
+
error: AssetServerCompilationError;
|
|
40
|
+
ok: false;
|
|
41
|
+
});
|
|
42
|
+
export declare function resolveStyle(record: StyleRecord, transformed: TransformedStyle, args: ResolveArgs): Promise<ResolveResult>;
|
|
43
|
+
export declare function resolveServedStyleOrThrow(filePath: string, args: ResolveArgs): {
|
|
44
|
+
identityPath: string;
|
|
45
|
+
stableUrlPathname: string;
|
|
46
|
+
};
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../src/lib/styles/resolve.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAA;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAEtD,KAAK,WAAW,GAAG,YAAY,CAAC,gBAAgB,EAAE,aAAa,EAAE,YAAY,CAAC,CAAA;AAE9E,KAAK,kBAAkB,GACnB;IACE,IAAI,EAAE,UAAU,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;CACpB,GACD;IACE,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,OAAO,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAEL,MAAM,MAAM,aAAa,GAAG;IAC1B,YAAY,EAAE,kBAAkB,EAAE,CAAA;IAClC,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;IACzB,YAAY,EAAE,MAAM,EAAE,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAA;IACxC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;IACzC,MAAM,EAAE,cAAc,CAAA;CACvB,CAAA;AAED,KAAK,aAAa,GAAG;IACnB,QAAQ,EAAE,cAAc,CAAA;CACzB,GAAG,CACA;IACE,EAAE,EAAE,IAAI,CAAA;IACR,KAAK,EAAE,aAAa,CAAA;CACrB,GACD;IACE,KAAK,EAAE,2BAA2B,CAAA;IAClC,EAAE,EAAE,KAAK,CAAA;CACV,CACJ,CAAA;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,WAAW,EACnB,WAAW,EAAE,gBAAgB,EAC7B,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,aAAa,CAAC,CAuDxB;AAED,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,WAAW,GAChB;IACD,YAAY,EAAE,MAAM,CAAA;IACpB,iBAAiB,EAAE,MAAM,CAAA;CAC1B,CA+BA"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { createAssetServerCompilationError, isAssetServerCompilationError, } from "../compilation-error.js";
|
|
4
|
+
import { normalizeFilePath } from "../paths.js";
|
|
5
|
+
export async function resolveStyle(record, transformed, args) {
|
|
6
|
+
let trackedFiles = new Set(transformed.trackedFiles);
|
|
7
|
+
let dependencies = [];
|
|
8
|
+
let deps = new Set();
|
|
9
|
+
for (let unresolved of transformed.unresolvedDependencies) {
|
|
10
|
+
let trackedFile = unresolved.type === 'import'
|
|
11
|
+
? getTrackedImportFilePath(unresolved.url, transformed.resolvedPath)
|
|
12
|
+
: null;
|
|
13
|
+
if (trackedFile && !args.isWatchIgnored(trackedFile)) {
|
|
14
|
+
trackedFiles.add(trackedFile);
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
let resolved = unresolved.type === 'import'
|
|
18
|
+
? resolveImportDependency(unresolved.url, transformed.resolvedPath, unresolved.placeholder, args)
|
|
19
|
+
: resolveUrlDependency(unresolved.url, unresolved.placeholder);
|
|
20
|
+
dependencies.push(resolved);
|
|
21
|
+
if (resolved.kind === 'local') {
|
|
22
|
+
if (!args.isWatchIgnored(resolved.depPath)) {
|
|
23
|
+
trackedFiles.add(resolved.depPath);
|
|
24
|
+
}
|
|
25
|
+
deps.add(resolved.depPath);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
return failResolve(error, trackedFiles, transformed.resolvedPath);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
ok: true,
|
|
34
|
+
tracking: {
|
|
35
|
+
trackedFiles: [...trackedFiles],
|
|
36
|
+
},
|
|
37
|
+
value: {
|
|
38
|
+
dependencies,
|
|
39
|
+
deps: [...deps],
|
|
40
|
+
fingerprint: transformed.fingerprint,
|
|
41
|
+
identityPath: record.identityPath,
|
|
42
|
+
rawCode: transformed.rawCode,
|
|
43
|
+
resolvedPath: transformed.resolvedPath,
|
|
44
|
+
sourceMap: transformed.sourceMap,
|
|
45
|
+
stableUrlPathname: transformed.stableUrlPathname,
|
|
46
|
+
trackedFiles: [...trackedFiles],
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function resolveServedStyleOrThrow(filePath, args) {
|
|
51
|
+
let identityPath = resolveExistingFilePath(filePath);
|
|
52
|
+
if (!identityPath) {
|
|
53
|
+
throw createAssetServerCompilationError(`File not found: ${filePath}`, {
|
|
54
|
+
code: 'FILE_NOT_FOUND',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (!isStyleFilePath(identityPath)) {
|
|
58
|
+
throw createAssetServerCompilationError(`File not found: ${identityPath}`, {
|
|
59
|
+
code: 'FILE_NOT_FOUND',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (!args.isAllowed(identityPath)) {
|
|
63
|
+
throw createAssetServerCompilationError(`File is not allowed: ${identityPath}`, {
|
|
64
|
+
code: 'FILE_NOT_ALLOWED',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
let stableUrlPathname = args.routes.toUrlPathname(identityPath);
|
|
68
|
+
if (!stableUrlPathname) {
|
|
69
|
+
throw createAssetServerCompilationError(`File ${identityPath} is outside all configured fileMap entries.`, {
|
|
70
|
+
code: 'FILE_OUTSIDE_FILE_MAP',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return { identityPath, stableUrlPathname };
|
|
74
|
+
}
|
|
75
|
+
function resolveImportDependency(url, importerPath, placeholder, args) {
|
|
76
|
+
if (isExternalUrl(url)) {
|
|
77
|
+
return {
|
|
78
|
+
kind: 'external',
|
|
79
|
+
placeholder,
|
|
80
|
+
replacement: url,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
let { pathname, suffix } = splitUrlSuffix(url);
|
|
84
|
+
if (pathname.length === 0 || pathname === '#') {
|
|
85
|
+
return {
|
|
86
|
+
kind: 'external',
|
|
87
|
+
placeholder,
|
|
88
|
+
replacement: url,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (pathname.startsWith('/')) {
|
|
92
|
+
return {
|
|
93
|
+
kind: 'external',
|
|
94
|
+
placeholder,
|
|
95
|
+
replacement: url,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
let resolvedFilePath = normalizeFilePath(path.resolve(path.dirname(importerPath), pathname));
|
|
99
|
+
let identityPath = resolveExistingFilePath(resolvedFilePath);
|
|
100
|
+
if (!identityPath || !isStyleFilePath(identityPath)) {
|
|
101
|
+
throw createAssetServerCompilationError(`Failed to resolve import "${url}" in ${importerPath}.`, {
|
|
102
|
+
code: 'IMPORT_RESOLUTION_FAILED',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (!args.isAllowed(identityPath)) {
|
|
106
|
+
throw createAssetServerCompilationError(`Import "${url}" in ${importerPath}, resolved to "${identityPath}", is not allowed by the asset server allow/deny configuration. ` +
|
|
107
|
+
`Add a matching allow rule for this file path, remove a conflicting deny rule for this file path, or mark this import as external.`, {
|
|
108
|
+
code: 'IMPORT_NOT_ALLOWED',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (!args.routes.toUrlPathname(identityPath)) {
|
|
112
|
+
throw createAssetServerCompilationError(`Import "${url}" in ${importerPath}, resolved to "${identityPath}", is outside all configured fileMap entries. ` +
|
|
113
|
+
`Add a matching fileMap entry for this file path, or mark this import as external.`, {
|
|
114
|
+
code: 'IMPORT_OUTSIDE_FILE_MAP',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
depPath: identityPath,
|
|
119
|
+
kind: 'local',
|
|
120
|
+
placeholder,
|
|
121
|
+
suffix,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function resolveUrlDependency(url, placeholder) {
|
|
125
|
+
return {
|
|
126
|
+
kind: 'external',
|
|
127
|
+
placeholder,
|
|
128
|
+
replacement: url,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function resolveExistingFilePath(filePath) {
|
|
132
|
+
try {
|
|
133
|
+
return normalizeFilePath(fs.realpathSync(filePath));
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
if (isNoEntityError(error))
|
|
137
|
+
return null;
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function splitUrlSuffix(url) {
|
|
142
|
+
let queryIndex = url.indexOf('?');
|
|
143
|
+
let hashIndex = url.indexOf('#');
|
|
144
|
+
let endIndex = [queryIndex, hashIndex].filter((index) => index >= 0).sort((a, b) => a - b)[0];
|
|
145
|
+
if (endIndex == null) {
|
|
146
|
+
return {
|
|
147
|
+
pathname: url,
|
|
148
|
+
suffix: '',
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
pathname: url.slice(0, endIndex),
|
|
153
|
+
suffix: url.slice(endIndex),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function getTrackedImportFilePath(specifier, importerPath) {
|
|
157
|
+
let { pathname } = splitUrlSuffix(specifier);
|
|
158
|
+
if (pathname.startsWith('./') || pathname.startsWith('../')) {
|
|
159
|
+
return normalizeFilePath(path.resolve(path.dirname(importerPath), pathname));
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
function isStyleFilePath(filePath) {
|
|
164
|
+
return path.extname(filePath).toLowerCase() === '.css';
|
|
165
|
+
}
|
|
166
|
+
function isExternalUrl(url) {
|
|
167
|
+
return url.startsWith('#') || url.startsWith('//') || /^[A-Za-z][A-Za-z\d+.-]*:/.test(url);
|
|
168
|
+
}
|
|
169
|
+
function isNoEntityError(error) {
|
|
170
|
+
return (error instanceof Error && 'code' in error && error.code === 'ENOENT');
|
|
171
|
+
}
|
|
172
|
+
function toResolveError(error, importerPath) {
|
|
173
|
+
if (isAssetServerCompilationError(error))
|
|
174
|
+
return error;
|
|
175
|
+
return createAssetServerCompilationError(`Failed to resolve imports in ${importerPath}. ${error instanceof Error ? error.message : String(error)}`, {
|
|
176
|
+
cause: error,
|
|
177
|
+
code: 'IMPORT_RESOLUTION_FAILED',
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function failResolve(error, trackedFiles, importerPath) {
|
|
181
|
+
return {
|
|
182
|
+
ok: false,
|
|
183
|
+
error: toResolveError(error, importerPath),
|
|
184
|
+
tracking: {
|
|
185
|
+
trackedFiles: [...trackedFiles],
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ModuleTracking } from '../module-store.ts';
|
|
2
|
+
import type { AssetServerCompilationError } from '../compilation-error.ts';
|
|
3
|
+
import type { CompiledRoutes } from '../routes.ts';
|
|
4
|
+
import type { ResolvedStyleTarget } from '../target.ts';
|
|
5
|
+
type TransformedStyleDependency = {
|
|
6
|
+
placeholder: string;
|
|
7
|
+
type: 'import';
|
|
8
|
+
url: string;
|
|
9
|
+
} | {
|
|
10
|
+
placeholder: string;
|
|
11
|
+
type: 'url';
|
|
12
|
+
url: string;
|
|
13
|
+
};
|
|
14
|
+
export type TransformedStyle = {
|
|
15
|
+
fingerprint: string | null;
|
|
16
|
+
identityPath: string;
|
|
17
|
+
rawCode: string;
|
|
18
|
+
resolvedPath: string;
|
|
19
|
+
sourceMap: string | null;
|
|
20
|
+
stableUrlPathname: string;
|
|
21
|
+
trackedFiles: string[];
|
|
22
|
+
unresolvedDependencies: TransformedStyleDependency[];
|
|
23
|
+
};
|
|
24
|
+
type TransformResult = {
|
|
25
|
+
tracking: ModuleTracking;
|
|
26
|
+
} & ({
|
|
27
|
+
ok: true;
|
|
28
|
+
value: TransformedStyle;
|
|
29
|
+
} | {
|
|
30
|
+
error: AssetServerCompilationError;
|
|
31
|
+
ok: false;
|
|
32
|
+
});
|
|
33
|
+
export type TransformArgs = {
|
|
34
|
+
buildId: string | null;
|
|
35
|
+
isWatchIgnored(filePath: string): boolean;
|
|
36
|
+
minify: boolean;
|
|
37
|
+
routes: CompiledRoutes;
|
|
38
|
+
sourceMaps: 'external' | 'inline' | null;
|
|
39
|
+
sourceMapSourcePaths: 'absolute' | 'url';
|
|
40
|
+
targets: ResolvedStyleTarget | null;
|
|
41
|
+
};
|
|
42
|
+
type TransformRecord = {
|
|
43
|
+
identityPath: string;
|
|
44
|
+
};
|
|
45
|
+
export declare function transformStyle(record: TransformRecord, args: TransformArgs): Promise<TransformResult>;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=transform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/lib/styles/transform.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAExD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAA;AAC1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAEvD,KAAK,0BAA0B,GAC3B;IACE,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,QAAQ,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;CACZ,GACD;IACE,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,KAAK,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAEL,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;IACzB,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,sBAAsB,EAAE,0BAA0B,EAAE,CAAA;CACrD,CAAA;AAED,KAAK,eAAe,GAAG;IACrB,QAAQ,EAAE,cAAc,CAAA;CACzB,GAAG,CACA;IACE,EAAE,EAAE,IAAI,CAAA;IACR,KAAK,EAAE,gBAAgB,CAAA;CACxB,GACD;IACE,KAAK,EAAE,2BAA2B,CAAA;IAClC,EAAE,EAAE,KAAK,CAAA;CACV,CACJ,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;IACzC,MAAM,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,cAAc,CAAA;IACtB,UAAU,EAAE,UAAU,GAAG,QAAQ,GAAG,IAAI,CAAA;IACxC,oBAAoB,EAAE,UAAU,GAAG,KAAK,CAAA;IACxC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAAA;CACpC,CAAA;AAED,KAAK,eAAe,GAAG;IACrB,YAAY,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,wBAAsB,cAAc,CAClC,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,eAAe,CAAC,CA6G1B"}
|