@remix-run/assets 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +241 -28
- package/dist/assets.d.ts +1 -0
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +1 -0
- package/dist/lib/access.d.ts +1 -0
- package/dist/lib/access.d.ts.map +1 -1
- package/dist/lib/access.js +10 -1
- package/dist/lib/asset-server.d.ts +33 -7
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +195 -23
- package/dist/lib/compilation-error.d.ts +1 -1
- package/dist/lib/compilation-error.d.ts.map +1 -1
- package/dist/lib/files/compiler.d.ts +71 -0
- package/dist/lib/files/compiler.d.ts.map +1 -0
- package/dist/lib/files/compiler.js +552 -0
- package/dist/lib/files/config.d.ts +101 -0
- package/dist/lib/files/config.d.ts.map +1 -0
- package/dist/lib/files/config.js +219 -0
- package/dist/lib/files/store.d.ts +31 -0
- package/dist/lib/files/store.d.ts.map +1 -0
- package/dist/lib/files/store.js +63 -0
- package/dist/lib/fingerprint.d.ts +3 -3
- package/dist/lib/fingerprint.d.ts.map +1 -1
- package/dist/lib/fingerprint.js +5 -5
- package/dist/lib/injected-packages.d.ts +11 -0
- package/dist/lib/injected-packages.d.ts.map +1 -0
- package/dist/lib/injected-packages.js +86 -0
- package/dist/lib/paths.d.ts +1 -0
- package/dist/lib/paths.d.ts.map +1 -1
- package/dist/lib/paths.js +12 -0
- package/dist/lib/routes.d.ts +5 -7
- package/dist/lib/routes.d.ts.map +1 -1
- package/dist/lib/routes.js +41 -36
- package/dist/lib/scripts/compiler.d.ts +1 -0
- package/dist/lib/scripts/compiler.d.ts.map +1 -1
- package/dist/lib/scripts/compiler.js +30 -5
- package/dist/lib/scripts/resolve.d.ts.map +1 -1
- package/dist/lib/scripts/resolve.js +57 -10
- package/dist/lib/scripts/transform.d.ts.map +1 -1
- package/dist/lib/scripts/transform.js +79 -3
- package/dist/lib/styles/compiler.d.ts +4 -0
- package/dist/lib/styles/compiler.d.ts.map +1 -1
- package/dist/lib/styles/compiler.js +2 -0
- package/dist/lib/styles/emit.d.ts +3 -0
- package/dist/lib/styles/emit.d.ts.map +1 -1
- package/dist/lib/styles/emit.js +36 -1
- package/dist/lib/styles/resolve.d.ts +8 -1
- package/dist/lib/styles/resolve.d.ts.map +1 -1
- package/dist/lib/styles/resolve.js +85 -32
- package/dist/lib/watch.d.ts +2 -0
- package/dist/lib/watch.d.ts.map +1 -1
- package/dist/lib/watch.js +25 -8
- package/package.json +11 -5
- package/src/assets.ts +1 -0
- package/src/lib/access.ts +10 -1
- package/src/lib/asset-server.ts +297 -34
- package/src/lib/compilation-error.ts +9 -0
- package/src/lib/files/compiler.ts +885 -0
- package/src/lib/files/config.ts +479 -0
- package/src/lib/files/store.ts +109 -0
- package/src/lib/fingerprint.ts +8 -7
- package/src/lib/injected-packages.ts +117 -0
- package/src/lib/paths.ts +28 -0
- package/src/lib/routes.ts +67 -55
- package/src/lib/scripts/compiler.ts +43 -5
- package/src/lib/scripts/resolve.ts +89 -11
- package/src/lib/scripts/transform.ts +105 -3
- package/src/lib/styles/compiler.ts +9 -0
- package/src/lib/styles/emit.ts +75 -1
- package/src/lib/styles/resolve.ts +132 -35
- package/src/lib/watch.ts +34 -12
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { supportedScriptExtensions } from "../scripts/resolve.js";
|
|
2
|
+
export function defineFileTransform(transform) {
|
|
3
|
+
return transform;
|
|
4
|
+
}
|
|
5
|
+
const reservedFileExtensions = new Set([...supportedScriptExtensions, '.css', '.map']);
|
|
6
|
+
const defaultMaxRequestTransforms = 5;
|
|
7
|
+
export function normalizeFilesOptions(files) {
|
|
8
|
+
if (files == null) {
|
|
9
|
+
return {
|
|
10
|
+
extensions: [],
|
|
11
|
+
globalTransforms: [],
|
|
12
|
+
hasTransforms: false,
|
|
13
|
+
maxRequestTransforms: defaultMaxRequestTransforms,
|
|
14
|
+
transforms: new Map(),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (!Array.isArray(files.extensions)) {
|
|
18
|
+
throw new TypeError('files.extensions must be an array');
|
|
19
|
+
}
|
|
20
|
+
let normalizedExtensions = [];
|
|
21
|
+
let seen = new Set();
|
|
22
|
+
for (let extension of files.extensions) {
|
|
23
|
+
if (typeof extension !== 'string') {
|
|
24
|
+
throw new TypeError('files.extensions values must be strings');
|
|
25
|
+
}
|
|
26
|
+
let normalizedExtension = extension.trim().toLowerCase();
|
|
27
|
+
if (!/^\.[A-Za-z0-9_-]+$/.test(normalizedExtension)) {
|
|
28
|
+
throw new TypeError(`files.extensions values must use ".ext" format. Received "${extension}".`);
|
|
29
|
+
}
|
|
30
|
+
if (reservedFileExtensions.has(normalizedExtension)) {
|
|
31
|
+
throw new TypeError(`files.extensions cannot include compiled asset extensions like "${normalizedExtension}".`);
|
|
32
|
+
}
|
|
33
|
+
if (seen.has(normalizedExtension))
|
|
34
|
+
continue;
|
|
35
|
+
seen.add(normalizedExtension);
|
|
36
|
+
normalizedExtensions.push(normalizedExtension);
|
|
37
|
+
}
|
|
38
|
+
let transforms = files.transforms ?? {};
|
|
39
|
+
if (transforms === null || typeof transforms !== 'object' || Array.isArray(transforms)) {
|
|
40
|
+
throw new TypeError('files.transforms must be an object');
|
|
41
|
+
}
|
|
42
|
+
let normalizedTransforms = new Map();
|
|
43
|
+
for (let [name, transform] of Object.entries(transforms)) {
|
|
44
|
+
if (!/^[A-Za-z0-9_-]+$/.test(name)) {
|
|
45
|
+
throw new TypeError(`files.transforms keys must use "transform-name" format. Received "${name}".`);
|
|
46
|
+
}
|
|
47
|
+
if (transform === null ||
|
|
48
|
+
typeof transform !== 'object' ||
|
|
49
|
+
typeof transform.transform !== 'function') {
|
|
50
|
+
throw new TypeError(`files.transforms.${name} must define a transform() function`);
|
|
51
|
+
}
|
|
52
|
+
if ('param' in transform &&
|
|
53
|
+
transform.param !== undefined &&
|
|
54
|
+
transform.param !== true &&
|
|
55
|
+
transform.param !== 'optional') {
|
|
56
|
+
throw new TypeError(`files.transforms.${name}.param must be true or "optional"`);
|
|
57
|
+
}
|
|
58
|
+
normalizedTransforms.set(name, {
|
|
59
|
+
...transform,
|
|
60
|
+
extensions: normalizeTransformExtensions(transform.extensions, `files.transforms.${name}.extensions`),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
let globalTransforms = files.globalTransforms ?? [];
|
|
64
|
+
if (!Array.isArray(globalTransforms)) {
|
|
65
|
+
throw new TypeError('files.globalTransforms must be an array');
|
|
66
|
+
}
|
|
67
|
+
let normalizedGlobalTransforms = [];
|
|
68
|
+
for (let [index, transform] of globalTransforms.entries()) {
|
|
69
|
+
if (typeof transform === 'function') {
|
|
70
|
+
normalizedGlobalTransforms.push({ transform });
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (transform === null || typeof transform !== 'object') {
|
|
74
|
+
throw new TypeError(`files.globalTransforms[${index}] must be a function or object`);
|
|
75
|
+
}
|
|
76
|
+
if ('name' in transform && transform.name !== undefined && typeof transform.name !== 'string') {
|
|
77
|
+
throw new TypeError(`files.globalTransforms[${index}].name must be a string`);
|
|
78
|
+
}
|
|
79
|
+
if (typeof transform.transform !== 'function') {
|
|
80
|
+
throw new TypeError(`files.globalTransforms[${index}] must define a transform() function`);
|
|
81
|
+
}
|
|
82
|
+
normalizedGlobalTransforms.push({
|
|
83
|
+
...transform,
|
|
84
|
+
extensions: normalizeTransformExtensions(transform.extensions, `files.globalTransforms[${index}].extensions`),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
let maxRequestTransforms = files.maxRequestTransforms ?? defaultMaxRequestTransforms;
|
|
88
|
+
if (!Number.isInteger(maxRequestTransforms) || maxRequestTransforms < 1) {
|
|
89
|
+
throw new TypeError('files.maxRequestTransforms must be a positive integer');
|
|
90
|
+
}
|
|
91
|
+
if (files.cache !== undefined) {
|
|
92
|
+
if (files.cache === null ||
|
|
93
|
+
typeof files.cache !== 'object' ||
|
|
94
|
+
typeof files.cache.get !== 'function' ||
|
|
95
|
+
typeof files.cache.set !== 'function') {
|
|
96
|
+
throw new TypeError('files.cache must implement the FileStorage interface');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
cache: files.cache,
|
|
101
|
+
extensions: normalizedExtensions,
|
|
102
|
+
globalTransforms: normalizedGlobalTransforms,
|
|
103
|
+
hasTransforms: normalizedTransforms.size > 0 || normalizedGlobalTransforms.length > 0,
|
|
104
|
+
maxRequestTransforms,
|
|
105
|
+
transforms: normalizedTransforms,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function normalizeTransformExtensions(extensions, optionPath) {
|
|
109
|
+
if (extensions === undefined)
|
|
110
|
+
return undefined;
|
|
111
|
+
if (!Array.isArray(extensions)) {
|
|
112
|
+
throw new TypeError(`${optionPath} must be an array`);
|
|
113
|
+
}
|
|
114
|
+
if (extensions.length === 0) {
|
|
115
|
+
throw new TypeError(`${optionPath} must include at least one extension`);
|
|
116
|
+
}
|
|
117
|
+
let normalizedExtensions = [];
|
|
118
|
+
let seen = new Set();
|
|
119
|
+
for (let extension of extensions) {
|
|
120
|
+
if (typeof extension !== 'string') {
|
|
121
|
+
throw new TypeError(`${optionPath} values must be strings`);
|
|
122
|
+
}
|
|
123
|
+
let normalizedExtension = extension.trim().toLowerCase();
|
|
124
|
+
if (!/^\.[A-Za-z0-9_-]+$/.test(normalizedExtension)) {
|
|
125
|
+
throw new TypeError(`${optionPath} values must use ".ext" format. Received "${extension}".`);
|
|
126
|
+
}
|
|
127
|
+
if (seen.has(normalizedExtension))
|
|
128
|
+
continue;
|
|
129
|
+
seen.add(normalizedExtension);
|
|
130
|
+
normalizedExtensions.push(normalizedExtension);
|
|
131
|
+
}
|
|
132
|
+
return normalizedExtensions;
|
|
133
|
+
}
|
|
134
|
+
export function serializeAssetTransformInvocations(transforms, transformsByName, maxTransforms = defaultMaxRequestTransforms) {
|
|
135
|
+
if (transforms.length > maxTransforms) {
|
|
136
|
+
throw new TypeError(`Expected at most ${maxTransforms} request transforms`);
|
|
137
|
+
}
|
|
138
|
+
return transforms.map((transformInvocation) => normalizeAssetTransformInvocation(transformInvocation, transformsByName, (message) => {
|
|
139
|
+
throw new TypeError(message);
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
export function parseAssetTransformInvocations(transformsQuery, transformsByName, maxTransforms = defaultMaxRequestTransforms) {
|
|
143
|
+
if (transformsQuery.length > maxTransforms) {
|
|
144
|
+
throw new TypeError(`Expected at most ${maxTransforms} request transforms`);
|
|
145
|
+
}
|
|
146
|
+
return transformsQuery.map((transformQuery) => parseSerializedAssetTransformInvocation(transformQuery, transformsByName, (message) => {
|
|
147
|
+
throw new TypeError(message);
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
function normalizeAssetTransformInvocation(transformInvocation, transformsByName, onError) {
|
|
151
|
+
if (typeof transformInvocation === 'string') {
|
|
152
|
+
if (!/^[A-Za-z0-9_-]+$/.test(transformInvocation)) {
|
|
153
|
+
return onError('Expected each transform name to use "transform-name" format');
|
|
154
|
+
}
|
|
155
|
+
let transform = transformsByName.get(transformInvocation);
|
|
156
|
+
if (transform === undefined) {
|
|
157
|
+
return onError(`Unknown file transform "${transformInvocation}"`);
|
|
158
|
+
}
|
|
159
|
+
if (transform.param === true) {
|
|
160
|
+
return onError(`File transform "${transformInvocation}" requires a param`);
|
|
161
|
+
}
|
|
162
|
+
return transformInvocation;
|
|
163
|
+
}
|
|
164
|
+
if (!Array.isArray(transformInvocation)) {
|
|
165
|
+
return onError('Expected each transform to be a string name or tuple');
|
|
166
|
+
}
|
|
167
|
+
if (transformInvocation.length === 0 || transformInvocation.length > 2) {
|
|
168
|
+
return onError('Expected each transform tuple to have one or two items');
|
|
169
|
+
}
|
|
170
|
+
let [name, rawParam] = transformInvocation;
|
|
171
|
+
if (typeof name !== 'string' || !/^[A-Za-z0-9_-]+$/.test(name)) {
|
|
172
|
+
return onError('Expected each transform name to use "transform-name" format');
|
|
173
|
+
}
|
|
174
|
+
let transform = transformsByName.get(name);
|
|
175
|
+
if (transform === undefined) {
|
|
176
|
+
return onError(`Unknown file transform "${name}"`);
|
|
177
|
+
}
|
|
178
|
+
if (transformInvocation.length === 1) {
|
|
179
|
+
if (transform.param === true) {
|
|
180
|
+
return onError(`File transform "${name}" requires a param`);
|
|
181
|
+
}
|
|
182
|
+
return name;
|
|
183
|
+
}
|
|
184
|
+
if (typeof rawParam !== 'string') {
|
|
185
|
+
return onError(`Invalid param for file transform "${name}": expected a string`);
|
|
186
|
+
}
|
|
187
|
+
if (transform.param === undefined) {
|
|
188
|
+
if (transformInvocation.length === 2) {
|
|
189
|
+
return onError(`File transform "${name}" does not accept a param`);
|
|
190
|
+
}
|
|
191
|
+
return name;
|
|
192
|
+
}
|
|
193
|
+
return `${name}:${rawParam}`;
|
|
194
|
+
}
|
|
195
|
+
function parseSerializedAssetTransformInvocation(transformQuery, transformsByName, onError) {
|
|
196
|
+
let separatorIndex = transformQuery.indexOf(':');
|
|
197
|
+
let name = separatorIndex === -1 ? transformQuery : transformQuery.slice(0, separatorIndex);
|
|
198
|
+
let param = separatorIndex === -1 ? undefined : transformQuery.slice(separatorIndex + 1);
|
|
199
|
+
if (!/^[A-Za-z0-9_-]+$/.test(name)) {
|
|
200
|
+
return onError('Expected each transform name to use "transform-name" format');
|
|
201
|
+
}
|
|
202
|
+
let transform = transformsByName.get(name);
|
|
203
|
+
if (transform === undefined) {
|
|
204
|
+
return onError(`Unknown file transform "${name}"`);
|
|
205
|
+
}
|
|
206
|
+
if (transform.param === undefined) {
|
|
207
|
+
if (param !== undefined) {
|
|
208
|
+
return onError(`File transform "${name}" does not accept a param`);
|
|
209
|
+
}
|
|
210
|
+
return name;
|
|
211
|
+
}
|
|
212
|
+
if (transform.param === true && param === undefined) {
|
|
213
|
+
return onError(`File transform "${name}" requires a param`);
|
|
214
|
+
}
|
|
215
|
+
if (param === undefined) {
|
|
216
|
+
return name;
|
|
217
|
+
}
|
|
218
|
+
return [name, param];
|
|
219
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type FileSnapshot = {
|
|
2
|
+
filePath: string;
|
|
3
|
+
mtimeNs: bigint;
|
|
4
|
+
size: bigint;
|
|
5
|
+
};
|
|
6
|
+
export type SourceFileMetadata = {
|
|
7
|
+
contentType: string;
|
|
8
|
+
etag: string;
|
|
9
|
+
extension: string;
|
|
10
|
+
fingerprint: string | null;
|
|
11
|
+
};
|
|
12
|
+
type SourceFileRecordState = {
|
|
13
|
+
metadata?: SourceFileMetadata;
|
|
14
|
+
metadataSnapshot?: FileSnapshot;
|
|
15
|
+
identityPath: string;
|
|
16
|
+
invalidationVersion: number;
|
|
17
|
+
staleMetadata?: SourceFileMetadata;
|
|
18
|
+
staleMetadataSnapshot?: FileSnapshot;
|
|
19
|
+
};
|
|
20
|
+
export type SourceFileRecord = Readonly<SourceFileRecordState>;
|
|
21
|
+
export type SourceFileStore = {
|
|
22
|
+
get(identityPath: string): SourceFileRecord;
|
|
23
|
+
invalidate(identityPath: string, options?: {
|
|
24
|
+
retainStale: boolean;
|
|
25
|
+
}): void;
|
|
26
|
+
invalidateForFileEvent(filePath: string, event: 'add' | 'change' | 'unlink'): void;
|
|
27
|
+
set(identityPath: string, metadata: SourceFileMetadata, snapshot: FileSnapshot | null): void;
|
|
28
|
+
};
|
|
29
|
+
export declare function createSourceFileStore(): SourceFileStore;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/lib/files/store.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B,CAAA;AAED,KAAK,qBAAqB,GAAG;IAC3B,QAAQ,CAAC,EAAE,kBAAkB,CAAA;IAC7B,gBAAgB,CAAC,EAAE,YAAY,CAAA;IAC/B,YAAY,EAAE,MAAM,CAAA;IACpB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,aAAa,CAAC,EAAE,kBAAkB,CAAA;IAClC,qBAAqB,CAAC,EAAE,YAAY,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC,qBAAqB,CAAC,CAAA;AAW9D,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,CAAC,YAAY,EAAE,MAAM,GAAG,gBAAgB,CAAA;IAC3C,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,WAAW,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAA;IAC1E,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAAA;IAClF,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAAA;CAC7F,CAAA;AAED,wBAAgB,qBAAqB,IAAI,eAAe,CAoEvD"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export function createSourceFileStore() {
|
|
2
|
+
let recordsByIdentityPath = new Map();
|
|
3
|
+
let invalidationVersionByIdentityPath = new Map();
|
|
4
|
+
return {
|
|
5
|
+
get(identityPath) {
|
|
6
|
+
return getOrCreateRecord(identityPath);
|
|
7
|
+
},
|
|
8
|
+
invalidate(identityPath, options = { retainStale: false }) {
|
|
9
|
+
let record = recordsByIdentityPath.get(identityPath);
|
|
10
|
+
if (!record)
|
|
11
|
+
return;
|
|
12
|
+
invalidateRecord(record, options);
|
|
13
|
+
},
|
|
14
|
+
invalidateForFileEvent(filePath, event) {
|
|
15
|
+
let record = recordsByIdentityPath.get(filePath);
|
|
16
|
+
if (!record)
|
|
17
|
+
return;
|
|
18
|
+
invalidateRecord(record, { retainStale: event === 'change' });
|
|
19
|
+
if (event === 'unlink') {
|
|
20
|
+
recordsByIdentityPath.delete(filePath);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
set(identityPath, metadata, snapshot) {
|
|
24
|
+
let record = recordsByIdentityPath.get(identityPath);
|
|
25
|
+
if (!record) {
|
|
26
|
+
record = getOrCreateRecord(identityPath);
|
|
27
|
+
}
|
|
28
|
+
record.metadata = metadata;
|
|
29
|
+
record.metadataSnapshot = snapshot ?? undefined;
|
|
30
|
+
record.staleMetadata = undefined;
|
|
31
|
+
record.staleMetadataSnapshot = undefined;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
function invalidateRecord(record, options) {
|
|
35
|
+
if (!options.retainStale) {
|
|
36
|
+
record.staleMetadata = undefined;
|
|
37
|
+
record.staleMetadataSnapshot = undefined;
|
|
38
|
+
}
|
|
39
|
+
else if (record.metadata && record.metadataSnapshot) {
|
|
40
|
+
record.staleMetadata = record.metadata;
|
|
41
|
+
record.staleMetadataSnapshot = record.metadataSnapshot;
|
|
42
|
+
}
|
|
43
|
+
else if (!record.staleMetadata || !record.staleMetadataSnapshot) {
|
|
44
|
+
record.staleMetadata = undefined;
|
|
45
|
+
record.staleMetadataSnapshot = undefined;
|
|
46
|
+
}
|
|
47
|
+
record.metadata = undefined;
|
|
48
|
+
record.metadataSnapshot = undefined;
|
|
49
|
+
record.invalidationVersion += 1;
|
|
50
|
+
invalidationVersionByIdentityPath.set(record.identityPath, record.invalidationVersion);
|
|
51
|
+
}
|
|
52
|
+
function getOrCreateRecord(identityPath) {
|
|
53
|
+
let existing = recordsByIdentityPath.get(identityPath);
|
|
54
|
+
if (existing)
|
|
55
|
+
return existing;
|
|
56
|
+
let record = {
|
|
57
|
+
identityPath,
|
|
58
|
+
invalidationVersion: invalidationVersionByIdentityPath.get(identityPath) ?? 0,
|
|
59
|
+
};
|
|
60
|
+
recordsByIdentityPath.set(identityPath, record);
|
|
61
|
+
return record;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export declare function hashContent(content: string): Promise<string>;
|
|
2
|
-
export declare function generateFingerprint(
|
|
1
|
+
export declare function hashContent(content: string | Uint8Array<ArrayBufferLike>): Promise<string>;
|
|
2
|
+
export declare function generateFingerprint(args: {
|
|
3
3
|
buildId: string;
|
|
4
|
-
content: string
|
|
4
|
+
content: string | Uint8Array<ArrayBufferLike>;
|
|
5
5
|
}): Promise<string>;
|
|
6
6
|
export declare function parseFingerprintSuffix(pathname: string): {
|
|
7
7
|
pathname: string;
|
|
@@ -1 +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,
|
|
1
|
+
{"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../../src/lib/fingerprint.ts"],"names":[],"mappings":"AAGA,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAIhG;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC,eAAe,CAAC,CAAA;CAC9C,GAAG,OAAO,CAAC,MAAM,CAAC,CAIlB;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"}
|
package/dist/lib/fingerprint.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
const fingerprintedExtensionRE = /^(.+)\.@([A-Za-z0-9_-]+)(\.[^./]+)$/;
|
|
2
2
|
const fingerprintedBasenameRE = /^(.+)\.@([A-Za-z0-9_-]+)$/;
|
|
3
3
|
export async function hashContent(content) {
|
|
4
|
-
let
|
|
5
|
-
let
|
|
6
|
-
let hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
4
|
+
let bytes = typeof content === 'string' ? new TextEncoder().encode(content) : Buffer.from(content);
|
|
5
|
+
let hashBuffer = await crypto.subtle.digest('SHA-256', bytes);
|
|
7
6
|
return Buffer.from(hashBuffer).toString('base64url').slice(0, 6);
|
|
8
7
|
}
|
|
9
|
-
export async function generateFingerprint(
|
|
10
|
-
|
|
8
|
+
export async function generateFingerprint(args) {
|
|
9
|
+
let content = typeof args.content === 'string' ? args.content : Buffer.from(args.content).toString('base64');
|
|
10
|
+
return hashContent(JSON.stringify([content, args.buildId]));
|
|
11
11
|
}
|
|
12
12
|
export function parseFingerprintSuffix(pathname) {
|
|
13
13
|
let lastSlashIndex = pathname.lastIndexOf('/');
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function isInjectedPackageFilePath(filePath: string): boolean;
|
|
2
|
+
export declare function getInjectedPackageRouteConfigs(): {
|
|
3
|
+
fileMap: Record<string, string>;
|
|
4
|
+
rootDir: string;
|
|
5
|
+
}[];
|
|
6
|
+
export declare function getInjectedPackageNameForSpecifier(specifier: string): string | null;
|
|
7
|
+
export declare function mayContainInjectedPackageSpecifier(sourceText: string): boolean;
|
|
8
|
+
export declare function maskAuthoredInjectedPackageSpecifier(specifier: string): string | null;
|
|
9
|
+
export declare function restoreAuthoredInjectedPackageSpecifier(specifier: string): string | null;
|
|
10
|
+
export declare function getInjectedPackageImporterPath(): string;
|
|
11
|
+
//# sourceMappingURL=injected-packages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injected-packages.d.ts","sourceRoot":"","sources":["../../src/lib/injected-packages.ts"],"names":[],"mappings":"AAeA,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAWnE;AAED,wBAAgB,8BAA8B,IAAI;IAChD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,OAAO,EAAE,MAAM,CAAA;CAChB,EAAE,CAWF;AAED,wBAAgB,kCAAkC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQnF;AAED,wBAAgB,kCAAkC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAE9E;AAED,wBAAgB,oCAAoC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMrF;AAED,wBAAgB,uCAAuC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYxF;AAMD,wBAAgB,8BAA8B,IAAI,MAAM,CAEvD"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { getFilePathDirectory, normalizeFilePath } from "./paths.js";
|
|
4
|
+
const injectedPackageNames = ['@oxc-project/runtime'];
|
|
5
|
+
const injectedPackagesBasePath = '/__@remix/injected';
|
|
6
|
+
const resolvedInjectedPackages = new Map();
|
|
7
|
+
export function isInjectedPackageFilePath(filePath) {
|
|
8
|
+
let normalizedFilePath = normalizeFilePath(filePath);
|
|
9
|
+
for (let packageName of injectedPackageNames) {
|
|
10
|
+
let packageRoot = getResolvedInjectedPackage(packageName).packageRoot;
|
|
11
|
+
if (normalizedFilePath === packageRoot || normalizedFilePath.startsWith(`${packageRoot}/`)) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
export function getInjectedPackageRouteConfigs() {
|
|
18
|
+
return injectedPackageNames.map((packageName) => {
|
|
19
|
+
let { packageRoot } = getResolvedInjectedPackage(packageName);
|
|
20
|
+
return {
|
|
21
|
+
fileMap: {
|
|
22
|
+
[getInjectedPackageRoutePattern(packageName)]: `${packageName}/*path`,
|
|
23
|
+
},
|
|
24
|
+
rootDir: getInjectedPackageRouteRoot(packageRoot, packageName),
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export function getInjectedPackageNameForSpecifier(specifier) {
|
|
29
|
+
for (let packageName of injectedPackageNames) {
|
|
30
|
+
if (specifier === packageName || specifier.startsWith(`${packageName}/`)) {
|
|
31
|
+
return packageName;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
export function mayContainInjectedPackageSpecifier(sourceText) {
|
|
37
|
+
return injectedPackageNames.some((packageName) => sourceText.includes(packageName));
|
|
38
|
+
}
|
|
39
|
+
export function maskAuthoredInjectedPackageSpecifier(specifier) {
|
|
40
|
+
let packageName = getInjectedPackageNameForSpecifier(specifier);
|
|
41
|
+
if (!packageName)
|
|
42
|
+
return null;
|
|
43
|
+
let maskedPackageName = getMaskedInjectedPackageName(packageName);
|
|
44
|
+
return `${maskedPackageName}${specifier.slice(packageName.length)}`;
|
|
45
|
+
}
|
|
46
|
+
export function restoreAuthoredInjectedPackageSpecifier(specifier) {
|
|
47
|
+
for (let packageName of injectedPackageNames) {
|
|
48
|
+
let maskedPackageName = getMaskedInjectedPackageName(packageName);
|
|
49
|
+
if (specifier === maskedPackageName) {
|
|
50
|
+
return packageName;
|
|
51
|
+
}
|
|
52
|
+
if (specifier.startsWith(`${maskedPackageName}/`)) {
|
|
53
|
+
return `${packageName}${specifier.slice(maskedPackageName.length)}`;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
function getMaskedInjectedPackageName(packageName) {
|
|
59
|
+
return `~${packageName.slice(1)}`;
|
|
60
|
+
}
|
|
61
|
+
export function getInjectedPackageImporterPath() {
|
|
62
|
+
return normalizeFilePath(fileURLToPath(import.meta.url));
|
|
63
|
+
}
|
|
64
|
+
function getResolvedInjectedPackage(packageName) {
|
|
65
|
+
let existing = resolvedInjectedPackages.get(packageName);
|
|
66
|
+
if (existing)
|
|
67
|
+
return existing;
|
|
68
|
+
let packageJsonUrl = import.meta.resolve(`${packageName}/package.json`);
|
|
69
|
+
let packageJsonPath = normalizeFilePath(fs.realpathSync(fileURLToPath(packageJsonUrl)));
|
|
70
|
+
let resolvedInjectedPackage = {
|
|
71
|
+
packageJsonPath,
|
|
72
|
+
packageRoot: normalizeFilePath(fs.realpathSync(getFilePathDirectory(packageJsonPath))),
|
|
73
|
+
};
|
|
74
|
+
resolvedInjectedPackages.set(packageName, resolvedInjectedPackage);
|
|
75
|
+
return resolvedInjectedPackage;
|
|
76
|
+
}
|
|
77
|
+
function getInjectedPackageRoutePattern(packageName) {
|
|
78
|
+
return `${injectedPackagesBasePath}/${packageName}/*path`;
|
|
79
|
+
}
|
|
80
|
+
function getInjectedPackageRouteRoot(packageRoot, packageName) {
|
|
81
|
+
let routeRoot = packageRoot;
|
|
82
|
+
for (let _segment of packageName.split('/')) {
|
|
83
|
+
routeRoot = getFilePathDirectory(routeRoot);
|
|
84
|
+
}
|
|
85
|
+
return routeRoot;
|
|
86
|
+
}
|
package/dist/lib/paths.d.ts
CHANGED
|
@@ -4,5 +4,6 @@ export declare function isAbsoluteFilePath(filePath: string): boolean;
|
|
|
4
4
|
export declare function normalizeFilePath(filePath: string): string;
|
|
5
5
|
export declare function resolveFilePath(rootDir: string, filePath: string): string;
|
|
6
6
|
export declare function getFilePathDirectory(filePath: string): string;
|
|
7
|
+
export declare function getRelativeFilePath(fromPath: string, toPath: string): string;
|
|
7
8
|
export declare function getFilePathBaseName(filePath: string): string;
|
|
8
9
|
//# sourceMappingURL=paths.d.ts.map
|
package/dist/lib/paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAKA,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQ1D;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG5D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAmB1D;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMzE;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE5D"}
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAKA,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQ1D;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG5D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAmB1D;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMzE;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA0B5E;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE5D"}
|
package/dist/lib/paths.js
CHANGED
|
@@ -42,6 +42,18 @@ export function resolveFilePath(rootDir, filePath) {
|
|
|
42
42
|
export function getFilePathDirectory(filePath) {
|
|
43
43
|
return path.posix.dirname(normalizeWindowsPath(filePath));
|
|
44
44
|
}
|
|
45
|
+
export function getRelativeFilePath(fromPath, toPath) {
|
|
46
|
+
let normalizedFromPath = normalizeFilePath(fromPath);
|
|
47
|
+
let normalizedToPath = normalizeFilePath(toPath);
|
|
48
|
+
if (normalizedFromPath.startsWith('//') || normalizedToPath.startsWith('//')) {
|
|
49
|
+
return normalizeWindowsPath(path.win32.relative(normalizedFromPath.replace(/\//g, '\\'), normalizedToPath.replace(/\//g, '\\')));
|
|
50
|
+
}
|
|
51
|
+
if (windowsDriveLetterRE.test(normalizedFromPath) ||
|
|
52
|
+
windowsDriveLetterRE.test(normalizedToPath)) {
|
|
53
|
+
return normalizeWindowsPath(path.win32.relative(normalizedFromPath.replace(/\//g, '\\'), normalizedToPath.replace(/\//g, '\\')));
|
|
54
|
+
}
|
|
55
|
+
return path.posix.relative(normalizedFromPath, normalizedToPath);
|
|
56
|
+
}
|
|
45
57
|
export function getFilePathBaseName(filePath) {
|
|
46
58
|
return path.posix.basename(normalizeWindowsPath(filePath));
|
|
47
59
|
}
|
package/dist/lib/routes.d.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
interface RouteConfig {
|
|
2
|
+
fileMap: Readonly<Record<string, string>>;
|
|
3
|
+
rootDir: string;
|
|
4
4
|
}
|
|
5
5
|
export interface CompiledRoutes {
|
|
6
6
|
resolveUrlPathname(pathname: string): string | null;
|
|
7
7
|
toUrlPathname(filePath: string): string | null;
|
|
8
8
|
}
|
|
9
|
-
export declare function compileRoutes(
|
|
10
|
-
|
|
11
|
-
rootDir: string;
|
|
12
|
-
}): CompiledRoutes;
|
|
9
|
+
export declare function compileRoutes(basePath: string, routeConfigs: readonly RouteConfig[]): CompiledRoutes;
|
|
10
|
+
export {};
|
|
13
11
|
//# sourceMappingURL=routes.d.ts.map
|
package/dist/lib/routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/lib/routes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/lib/routes.ts"],"names":[],"mappings":"AAiBA,UAAU,WAAW;IACnB,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACzC,OAAO,EAAE,MAAM,CAAA;CAChB;AAUD,MAAM,WAAW,cAAc;IAC7B,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACnD,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAC/C;AAYD,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,SAAS,WAAW,EAAE,GACnC,cAAc,CA8ChB"}
|
package/dist/lib/routes.js
CHANGED
|
@@ -1,28 +1,32 @@
|
|
|
1
|
-
import * as path from 'node:path';
|
|
2
1
|
import { RoutePattern } from '@remix-run/route-pattern';
|
|
3
|
-
import {
|
|
2
|
+
import { createHref } from '@remix-run/route-pattern/href';
|
|
3
|
+
import { createMatcher } from '@remix-run/route-pattern/match';
|
|
4
|
+
import { getRelativeFilePath, isAbsoluteFilePath, normalizeFilePath, normalizePathname, resolveFilePath, } from "./paths.js";
|
|
4
5
|
function normalizeFilePattern(pattern) {
|
|
5
6
|
if (isAbsoluteFilePath(pattern)) {
|
|
6
7
|
throw new Error(`File route patterns must be relative to the asset server root.\nPattern: ${pattern}`);
|
|
7
8
|
}
|
|
8
9
|
return normalizePathname(pattern);
|
|
9
10
|
}
|
|
10
|
-
export function compileRoutes(
|
|
11
|
-
if (Object.keys(
|
|
11
|
+
export function compileRoutes(basePath, routeConfigs) {
|
|
12
|
+
if (routeConfigs.every((routeConfig) => Object.keys(routeConfig.fileMap).length === 0)) {
|
|
12
13
|
throw new Error('createAssetServer() requires at least one configured fileMap entry.');
|
|
13
14
|
}
|
|
14
|
-
let compiledRoutes = Object.entries(
|
|
15
|
-
urlPattern,
|
|
15
|
+
let compiledRoutes = routeConfigs.flatMap((routeConfig) => Object.entries(routeConfig.fileMap).map(([urlPattern, filePattern]) => compileRoute({
|
|
16
16
|
filePattern,
|
|
17
|
-
|
|
17
|
+
urlPattern,
|
|
18
|
+
}, {
|
|
19
|
+
basePath,
|
|
20
|
+
rootDir: routeConfig.rootDir,
|
|
21
|
+
})));
|
|
18
22
|
return {
|
|
19
23
|
resolveUrlPathname(pathname) {
|
|
20
24
|
let normalizedPathname = normalizePathname(pathname);
|
|
21
25
|
for (let route of compiledRoutes) {
|
|
22
|
-
let match = route.
|
|
26
|
+
let match = route.urlMatcher.match(`http://remix.run${normalizedPathname}`);
|
|
23
27
|
if (!match)
|
|
24
28
|
continue;
|
|
25
|
-
let relativeFilePath = route.filePattern
|
|
29
|
+
let relativeFilePath = createHref(route.filePattern, match.params).replace(/^\/+/, '');
|
|
26
30
|
return resolveFilePath(route.rootDir, relativeFilePath);
|
|
27
31
|
}
|
|
28
32
|
return null;
|
|
@@ -30,65 +34,66 @@ export function compileRoutes(options) {
|
|
|
30
34
|
toUrlPathname(filePath) {
|
|
31
35
|
let normalizedFilePath = normalizeFilePath(filePath);
|
|
32
36
|
for (let route of compiledRoutes) {
|
|
33
|
-
let relativeFilePath = getRelativeFilePath(
|
|
34
|
-
|
|
35
|
-
continue;
|
|
36
|
-
let match = route.filePattern.ast.pathname.match(relativeFilePath);
|
|
37
|
+
let relativeFilePath = getRelativeFilePath(route.rootDir, normalizedFilePath);
|
|
38
|
+
let match = route.fileMatcher.match(`http://remix.run/${relativeFilePath}`);
|
|
37
39
|
if (!match)
|
|
38
40
|
continue;
|
|
39
|
-
return normalizePathname(route.urlPattern
|
|
41
|
+
return normalizePathname(createHref(route.urlPattern, match.params));
|
|
40
42
|
}
|
|
41
43
|
return null;
|
|
42
44
|
},
|
|
43
45
|
};
|
|
44
46
|
}
|
|
45
47
|
function compileRoute(route, options) {
|
|
46
|
-
let
|
|
48
|
+
let basePath = normalizePathname(options.basePath).replace(/\/+$/, '') || '/';
|
|
49
|
+
let relativeUrlPattern = normalizePathname(route.urlPattern);
|
|
50
|
+
let urlPatternSource = normalizePathname(`${basePath.replace(/\/+$/, '')}/${relativeUrlPattern.replace(/^\/+/, '')}`);
|
|
47
51
|
let filePatternSource = normalizeFilePattern(route.filePattern);
|
|
48
|
-
let urlPattern =
|
|
49
|
-
let filePattern =
|
|
52
|
+
let urlPattern = RoutePattern.parse(urlPatternSource);
|
|
53
|
+
let filePattern = RoutePattern.parse(filePatternSource);
|
|
50
54
|
validateNoUnnamedWildcards(urlPattern, 'URL');
|
|
51
55
|
validateNoUnnamedWildcards(filePattern, 'File');
|
|
52
56
|
validateRoutePatterns(urlPattern, filePattern);
|
|
53
57
|
return {
|
|
54
58
|
rootDir: normalizeFilePath(options.rootDir).replace(/\/+$/, ''),
|
|
55
59
|
urlPattern,
|
|
60
|
+
urlMatcher: createMatcher(urlPattern),
|
|
56
61
|
filePattern,
|
|
62
|
+
fileMatcher: createMatcher(stripDotSegments(filePatternSource)),
|
|
57
63
|
};
|
|
58
64
|
}
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
function getPathnameParams(pattern, match) {
|
|
65
|
-
let params = {};
|
|
66
|
-
for (let param of pattern.ast.pathname.params) {
|
|
67
|
-
if (param.name === '*')
|
|
65
|
+
function stripDotSegments(pattern) {
|
|
66
|
+
let segments = [];
|
|
67
|
+
for (let segment of pattern.split('/')) {
|
|
68
|
+
if (segment === '' || segment === '.')
|
|
68
69
|
continue;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
for (let param of match) {
|
|
72
|
-
if (param.name === '*')
|
|
70
|
+
if (segment === '..') {
|
|
71
|
+
segments.pop();
|
|
73
72
|
continue;
|
|
74
|
-
|
|
73
|
+
}
|
|
74
|
+
segments.push(segment);
|
|
75
75
|
}
|
|
76
|
-
return
|
|
76
|
+
return segments.join('/');
|
|
77
77
|
}
|
|
78
78
|
function validateRoutePatterns(urlPattern, filePattern) {
|
|
79
|
-
let urlParams = urlPattern
|
|
80
|
-
let fileParams = filePattern
|
|
79
|
+
let urlParams = getPathnameParams(urlPattern);
|
|
80
|
+
let fileParams = getPathnameParams(filePattern);
|
|
81
81
|
if (urlParams.length !== fileParams.length) {
|
|
82
82
|
throw new Error(`Route patterns must have matching capture structure.\nURL: ${urlPattern}\nFile: ${filePattern}`);
|
|
83
83
|
}
|
|
84
84
|
for (let i = 0; i < urlParams.length; i++) {
|
|
85
|
-
|
|
85
|
+
let urlParam = urlParams[i];
|
|
86
|
+
let fileParam = fileParams[i];
|
|
87
|
+
if (urlParam.type !== fileParam.type || urlParam.name !== fileParam.name) {
|
|
86
88
|
throw new Error(`Route patterns must have matching capture structure.\nURL: ${urlPattern}\nFile: ${filePattern}`);
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
function validateNoUnnamedWildcards(pattern, label) {
|
|
91
|
-
if (pattern.
|
|
93
|
+
if (pattern.pathname.tokens.some((token) => token.type === '*' && token.name === '*')) {
|
|
92
94
|
throw new Error(`${label} route patterns must use named wildcards for reversible mapping.\nPattern: ${pattern}`);
|
|
93
95
|
}
|
|
94
96
|
}
|
|
97
|
+
function getPathnameParams(pattern) {
|
|
98
|
+
return pattern.pathname.tokens.filter((token) => token.type === ':' || token.type === '*');
|
|
99
|
+
}
|