vitecache 0.2.1 → 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 +17 -26
- package/dist/browser.d.ts +9 -6
- package/dist/browser.js +28 -159
- package/dist/browser.js.map +1 -1
- package/dist/discover.d.ts +15 -0
- package/dist/discover.js +131 -0
- package/dist/discover.js.map +1 -0
- package/dist/disk.d.ts +2 -9
- package/dist/disk.js +9 -63
- package/dist/disk.js.map +1 -1
- package/dist/entry.d.ts +31 -10
- package/dist/entry.js +11 -94
- package/dist/entry.js.map +1 -1
- package/dist/node.d.ts +9 -6
- package/dist/node.js +101 -6
- package/dist/node.js.map +1 -1
- package/dist/shared.d.ts +17 -0
- package/dist/shared.js +5 -0
- package/dist/shared.js.map +1 -0
- package/dist/vite.d.ts +12 -0
- package/dist/vite.js +253 -0
- package/dist/vite.js.map +1 -0
- package/package.json +17 -9
- package/skills/vitecache/SKILL.md +46 -37
- package/dist/dependencies.d.ts +0 -15
- package/dist/dependencies.js +0 -131
- package/dist/dependencies.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/key.d.ts +0 -3
- package/dist/key.js +0 -10
- package/dist/key.js.map +0 -1
- package/dist/packages.d.ts +0 -1
- package/dist/packages.js +0 -27
- package/dist/packages.js.map +0 -1
- package/dist/storage.d.ts +0 -7
- package/dist/storage.js +0 -162
- package/dist/storage.js.map +0 -1
- package/dist/types.d.ts +0 -79
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/vite/index.d.ts +0 -8
- package/dist/vite/index.js +0 -220
- package/dist/vite/index.js.map +0 -1
- package/dist/vite/manifest.d.ts +0 -3
- package/dist/vite/manifest.js +0 -34
- package/dist/vite/manifest.js.map +0 -1
- package/dist/vite/resolve.d.ts +0 -8
- package/dist/vite/resolve.js +0 -83
- package/dist/vite/resolve.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,41 +1,32 @@
|
|
|
1
1
|
# vitecache
|
|
2
2
|
|
|
3
|
-
Cache expensive computation as bytes
|
|
3
|
+
Cache expensive computation as bytes: the Vite plugin computes it once in Node, then browsers and Node reuse it.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
pnpm add vitecache
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
+
Wrap a computation in a class named `Cached…Loader` and load with literal arguments:
|
|
10
|
+
|
|
9
11
|
```ts
|
|
10
12
|
import { CacheEntry } from "vitecache"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
let bytes = await entry.load()
|
|
23
|
-
|
|
24
|
-
if (bytes === undefined) {
|
|
25
|
-
bytes = await simplify()
|
|
26
|
-
await entry.save(bytes)
|
|
13
|
+
|
|
14
|
+
export class CachedMeshLoader {
|
|
15
|
+
async loadAsync(settings: { ratio: number }) {
|
|
16
|
+
const entry = new CacheEntry({ version: "simplify@2", dependencies: { settings: { value: settings } } })
|
|
17
|
+
let bytes = await entry.load()
|
|
18
|
+
if (bytes === undefined) {
|
|
19
|
+
bytes = await simplify(settings)
|
|
20
|
+
await entry.save(bytes)
|
|
21
|
+
}
|
|
22
|
+
return bytes
|
|
23
|
+
}
|
|
27
24
|
}
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
const mesh = await new CachedMeshLoader().loadAsync({ ratio: 0.5 })
|
|
30
27
|
```
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Paths are always relative to the project root: `root` in Node (the working directory by default), or the Vite project root in browsers. The Vite plugin resolves served URLs to their source paths and preserves mappings for public files and emitted assets in production builds; Node accepts local file URLs.
|
|
35
|
-
|
|
36
|
-
Package installation paths are excluded from identity; transitive packages need their own declarations. Load before computing so saving can detect dependencies that changed during computation.
|
|
37
|
-
|
|
38
|
-
Node reads and writes `<project_root>/public/_vitecache/`. Add the plugin to save browser results into the same directory during development:
|
|
29
|
+
Add the plugin:
|
|
39
30
|
|
|
40
31
|
```ts
|
|
41
32
|
import { defineConfig } from "vite"
|
|
@@ -44,6 +35,6 @@ import { vitecache } from "vitecache/vite"
|
|
|
44
35
|
export default defineConfig({ plugins: [vitecache()] })
|
|
45
36
|
```
|
|
46
37
|
|
|
47
|
-
|
|
38
|
+
The plugin finds these loads by scanning the project's source files, computes them in Node during development and builds, and deletes entries no load uses. Browsers read the saved bytes.
|
|
48
39
|
|
|
49
40
|
For usage patterns, see the [vitecache skill](skills/vitecache/SKILL.md), also shipped in this package.
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
export declare class CacheEntry extends Entry {
|
|
1
|
+
import { type CacheEntryOptions, type SaveOptions } from './entry.js';
|
|
2
|
+
export type { CacheEntryOptions, Dependency, Input, JsonValue, SaveOptions } from './entry.js';
|
|
3
|
+
/** A cached result, read from the cache directory the app serves. Only Node writes entries. */
|
|
4
|
+
export declare class CacheEntry {
|
|
5
|
+
#private;
|
|
7
6
|
constructor(options: CacheEntryOptions);
|
|
7
|
+
/** The saved bytes, or undefined when the app serves none. */
|
|
8
|
+
load(): Promise<Uint8Array | undefined>;
|
|
9
|
+
/** Writes nothing, since browsers only read. Warns once per version that its result was computed here. */
|
|
10
|
+
save(_value: Uint8Array, _options?: SaveOptions): Promise<void>;
|
|
8
11
|
}
|
package/dist/browser.js
CHANGED
|
@@ -1,164 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// Static SPA servers may return index.html for unknown paths.
|
|
12
|
-
if (!response.headers.get('content-type')?.includes('application/json'))
|
|
13
|
-
return false;
|
|
14
|
-
const data = await response.json();
|
|
15
|
-
return typeof data === 'object' && data !== null && 'vitecache' in data && data.vitecache === 1;
|
|
16
|
-
}
|
|
17
|
-
function discovery(base) {
|
|
18
|
-
const existing = discoveries.get(base.href);
|
|
19
|
-
if (existing)
|
|
20
|
-
return existing;
|
|
21
|
-
const pending = discover(base).catch((error) => {
|
|
22
|
-
discoveries.delete(base.href);
|
|
23
|
-
throw error;
|
|
24
|
-
});
|
|
25
|
-
discoveries.set(base.href, pending);
|
|
26
|
-
return pending;
|
|
27
|
-
}
|
|
28
|
-
/** The plugin answers a missing file or package dependency with 404 and a JSON reason. */
|
|
29
|
-
async function rejectMissing(response) {
|
|
30
|
-
if (response.status !== 404 || !response.headers.get('content-type')?.includes('application/json'))
|
|
31
|
-
return;
|
|
32
|
-
const error = await response.json();
|
|
33
|
-
if (typeof error === 'object' && error !== null && 'error' in error && error.error === 'missing-dependency' && 'message' in error && typeof error.message === 'string') {
|
|
34
|
-
throw new MissingDependencyError(error.message);
|
|
1
|
+
import { key } from './entry.js';
|
|
2
|
+
const warned = new Set();
|
|
3
|
+
/** A cached result, read from the cache directory the app serves. Only Node writes entries. */
|
|
4
|
+
export class CacheEntry {
|
|
5
|
+
#version;
|
|
6
|
+
#url;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.#version = options.version;
|
|
9
|
+
const base = new URL((options.basePath ?? '/_vitecache/').replace(/\/?$/, '/'), globalThis.location.href);
|
|
10
|
+
this.#url = new URL(`${key(options)}.bin`, base);
|
|
35
11
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
12
|
+
/** The saved bytes, or undefined when the app serves none. */
|
|
13
|
+
async load() {
|
|
14
|
+
// An entry keeps its name when its bytes change, so the HTTP cache must revalidate them.
|
|
15
|
+
const response = await fetch(this.#url, { cache: 'no-cache' });
|
|
16
|
+
if (response.status === 404)
|
|
17
|
+
return undefined;
|
|
18
|
+
if (!response.ok)
|
|
19
|
+
throw new Error(`vitecache load failed: HTTP ${response.status}`);
|
|
20
|
+
// Vite's dev server and SPA hosts answer a missing file with index.html.
|
|
21
|
+
if (response.headers.get('content-type')?.startsWith('text/html'))
|
|
22
|
+
return undefined;
|
|
23
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
44
24
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const url = new URL(locator.url, globalThis.location.href);
|
|
52
|
-
if (url.origin !== new URL(globalThis.location.href).origin)
|
|
53
|
-
throw new Error('URL dependencies must be served by the same Vite project origin.');
|
|
54
|
-
return [name, { url: url.href }];
|
|
55
|
-
});
|
|
56
|
-
if (locators.every(([, locator]) => locator.url === undefined)) {
|
|
57
|
-
return resolvedDependencies(Object.fromEntries(locators));
|
|
58
|
-
}
|
|
59
|
-
if (await plugin()) {
|
|
60
|
-
const response = await fetch(new URL('__vitecache/resolve', base), {
|
|
61
|
-
method: 'POST',
|
|
62
|
-
headers: { 'Content-Type': 'application/json' },
|
|
63
|
-
body: JSON.stringify({ dependencies: Object.fromEntries(locators) }),
|
|
64
|
-
cache: 'no-store',
|
|
65
|
-
});
|
|
66
|
-
await rejectMissing(response);
|
|
67
|
-
if (!response.ok)
|
|
68
|
-
throw new Error(`vitecache URL resolution failed: HTTP ${response.status}`);
|
|
69
|
-
const data = await response.json();
|
|
70
|
-
const resolved = resolvedDependencies(record(data).dependencies);
|
|
71
|
-
if (Object.keys(resolved).length !== locators.length || !locators.every(([name]) => Object.hasOwn(resolved, name))) {
|
|
72
|
-
throw new Error('Invalid vitecache resolved dependencies.');
|
|
73
|
-
}
|
|
74
|
-
return resolved;
|
|
75
|
-
}
|
|
76
|
-
const manifestUrl = new URL('__vitecache/paths.json', base);
|
|
77
|
-
const response = await fetch(manifestUrl, { cache: 'no-store' });
|
|
78
|
-
if (!response.ok || !response.headers.get('content-type')?.includes('application/json')) {
|
|
79
|
-
throw new Error('URL dependencies require the vitecache Vite plugin during development and build. Missing production URL mapping.');
|
|
80
|
-
}
|
|
81
|
-
const data = await response.json();
|
|
82
|
-
const manifest = record(data);
|
|
83
|
-
const mapping = paths(manifest.paths);
|
|
84
|
-
if (typeof manifest.root !== 'string' || !/^(?:\.\.\/)+$/.test(manifest.root))
|
|
85
|
-
throw new Error('Invalid vitecache production mapping root.');
|
|
86
|
-
const projectRoot = new URL(manifest.root, manifestUrl);
|
|
87
|
-
const mapped = Object.fromEntries(Object.entries(mapping).map(([url, path]) => [new URL(url, projectRoot).pathname, path]));
|
|
88
|
-
const resolved = locators.map(([name, locator]) => {
|
|
89
|
-
if (locator.url === undefined)
|
|
90
|
-
return [name, locator];
|
|
91
|
-
const pathname = new URL(locator.url).pathname;
|
|
92
|
-
const path = mapped[pathname];
|
|
93
|
-
// The build maps every file it deployed, so an unmapped URL has no backing file, as the plugin reports in development.
|
|
94
|
-
if (path === undefined)
|
|
95
|
-
throw new MissingDependencyError(`No vitecache source file mapping for URL: ${pathname}`);
|
|
96
|
-
return [name, { path }];
|
|
97
|
-
});
|
|
98
|
-
return resolvedDependencies(Object.fromEntries(resolved));
|
|
99
|
-
},
|
|
100
|
-
async snapshot(dependencies) {
|
|
101
|
-
if (!await plugin())
|
|
102
|
-
return { status: 'unavailable' };
|
|
103
|
-
const response = await fetch(new URL('__vitecache/snapshot', base), {
|
|
104
|
-
method: 'POST',
|
|
105
|
-
headers: { 'Content-Type': 'application/json' },
|
|
106
|
-
body: JSON.stringify({ dependencies }),
|
|
107
|
-
cache: 'no-store',
|
|
108
|
-
});
|
|
109
|
-
if (!response.ok)
|
|
110
|
-
throw new Error(`vitecache dependency validation failed: HTTP ${response.status}`);
|
|
111
|
-
const data = await response.json();
|
|
112
|
-
if (typeof data === 'object' && data !== null && 'status' in data && data.status === 'missing')
|
|
113
|
-
return { status: 'missing' };
|
|
114
|
-
if (typeof data !== 'object' || data === null || !('status' in data) || data.status !== 'available' || !('hashes' in data)) {
|
|
115
|
-
throw new TypeError('Invalid vitecache dependency response.');
|
|
116
|
-
}
|
|
117
|
-
return { status: 'available', hashes: hashes(data.hashes) };
|
|
118
|
-
},
|
|
119
|
-
async load(key, validate) {
|
|
120
|
-
const response = await fetch(new URL(`${key}.bin`, base), { cache: 'no-store' });
|
|
121
|
-
// The plugin answers misses with 204 so they stay out of the console; static hosts answer 404.
|
|
122
|
-
if (response.status === 204 || response.status === 404)
|
|
123
|
-
return undefined;
|
|
124
|
-
if (!response.ok)
|
|
125
|
-
throw new Error(`vitecache load failed: HTTP ${response.status}`);
|
|
126
|
-
const contentType = response.headers.get('content-type')?.split(';')[0]?.trim();
|
|
127
|
-
if (contentType === 'text/html')
|
|
128
|
-
return undefined;
|
|
129
|
-
if (contentType !== 'application/octet-stream')
|
|
130
|
-
throw new Error(`Unexpected vitecache content type: ${contentType ?? 'missing'}`);
|
|
131
|
-
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
132
|
-
if (!validate)
|
|
133
|
-
return { bytes };
|
|
134
|
-
const header = response.headers.get('X-Vitecache-Dependencies');
|
|
135
|
-
if (header === null)
|
|
136
|
-
throw new Error('Missing vitecache dependency metadata.');
|
|
137
|
-
const value = JSON.parse(decodeURIComponent(header));
|
|
138
|
-
return { bytes, hashes: hashes(value) };
|
|
139
|
-
},
|
|
140
|
-
async save(key, value, validation, expected, saved) {
|
|
141
|
-
// Metadata travels in the body: headers are capped near 16 KiB, and saved dependencies can be many.
|
|
142
|
-
const metadata = new TextEncoder().encode(JSON.stringify({ dependencies: validation, hashes: expected, saved }));
|
|
143
|
-
const body = new Uint8Array(4 + metadata.byteLength + value.byteLength);
|
|
144
|
-
new DataView(body.buffer).setUint32(0, metadata.byteLength);
|
|
145
|
-
body.set(metadata, 4);
|
|
146
|
-
body.set(value, 4 + metadata.byteLength);
|
|
147
|
-
const response = await fetch(new URL(`${key}.bin`, base), {
|
|
148
|
-
method: 'PUT',
|
|
149
|
-
headers: { 'Content-Type': 'application/octet-stream' },
|
|
150
|
-
body,
|
|
151
|
-
});
|
|
152
|
-
await rejectMissing(response);
|
|
153
|
-
if (!response.ok)
|
|
154
|
-
throw new Error(`vitecache save failed: HTTP ${response.status}`);
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
/** A persistent binary computation result. Pass a browser basePath matching the public cache URL, including any Vite base. */
|
|
159
|
-
export class CacheEntry extends Entry {
|
|
160
|
-
constructor(options) {
|
|
161
|
-
super(options, createBackend(options));
|
|
25
|
+
/** Writes nothing, since browsers only read. Warns once per version that its result was computed here. */
|
|
26
|
+
async save(_value, _options) {
|
|
27
|
+
if (warned.has(this.#version))
|
|
28
|
+
return;
|
|
29
|
+
warned.add(this.#version);
|
|
30
|
+
console.warn(`vitecache: ${this.#version} was computed in the browser, which does not save it.`);
|
|
162
31
|
}
|
|
163
32
|
}
|
|
164
33
|
//# sourceMappingURL=browser.js.map
|
package/dist/browser.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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","sourcesContent":["import { hashes, MissingDependencyError, paths, record, resolvedDependencies } from './dependencies.js';\nimport { Entry } from './entry.js';\nimport type { Backend, CacheEntryOptions, Dependency, ResolvedDependency } from './types.js';\nexport { MissingDependencyError } from './dependencies.js';\nexport type { CacheEntryOptions, Dependency, Dependencies, JsonValue, SavedDependencies, SavedDependency, SaveOptions } from './types.js';\n\nconst discoveries = new Map<string, Promise<boolean>>();\n\nasync function discover(base: URL): Promise<boolean> {\n const response = await fetch(new URL('__vitecache', base), { cache: 'no-store' });\n if (response.status === 404) return false;\n if (!response.ok) throw new Error(`vitecache discovery failed: HTTP ${response.status}`);\n // Static SPA servers may return index.html for unknown paths.\n if (!response.headers.get('content-type')?.includes('application/json')) return false;\n const data: unknown = await response.json();\n return typeof data === 'object' && data !== null && 'vitecache' in data && data.vitecache === 1;\n}\n\nfunction discovery(base: URL): Promise<boolean> {\n const existing = discoveries.get(base.href);\n if (existing) return existing;\n const pending = discover(base).catch((error: unknown) => {\n discoveries.delete(base.href);\n throw error;\n });\n discoveries.set(base.href, pending);\n return pending;\n}\n\n/** The plugin answers a missing file or package dependency with 404 and a JSON reason. */\nasync function rejectMissing(response: Response): Promise<void> {\n if (response.status !== 404 || !response.headers.get('content-type')?.includes('application/json')) return;\n const error: unknown = await response.json();\n if (typeof error === 'object' && error !== null && 'error' in error && error.error === 'missing-dependency' && 'message' in error && typeof error.message === 'string') {\n throw new MissingDependencyError(error.message);\n }\n}\n\nfunction createBackend(options: CacheEntryOptions): Backend {\n const base = new URL(options.basePath ?? '/_vitecache/', globalThis.location.href);\n base.pathname = `${base.pathname.replace(/\\/+$/, '')}/`;\n base.search = '';\n base.hash = '';\n\n function plugin(): Promise<boolean> {\n return discovery(base);\n }\n\n return {\n canSave: plugin,\n async resolve(dependencies) {\n const locators = Object.entries(dependencies).map(([name, locator]): [string, Dependency] => {\n if (locator.url === undefined) return [name, locator];\n const url = new URL(locator.url, globalThis.location.href);\n if (url.origin !== new URL(globalThis.location.href).origin) throw new Error('URL dependencies must be served by the same Vite project origin.');\n return [name, { url: url.href }];\n });\n if (locators.every(([, locator]) => locator.url === undefined)) {\n return resolvedDependencies(Object.fromEntries(locators));\n }\n if (await plugin()) {\n const response = await fetch(new URL('__vitecache/resolve', base), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ dependencies: Object.fromEntries(locators) }),\n cache: 'no-store',\n });\n await rejectMissing(response);\n if (!response.ok) throw new Error(`vitecache URL resolution failed: HTTP ${response.status}`);\n const data: unknown = await response.json();\n const resolved = resolvedDependencies(record(data).dependencies);\n if (Object.keys(resolved).length !== locators.length || !locators.every(([name]) => Object.hasOwn(resolved, name))) {\n throw new Error('Invalid vitecache resolved dependencies.');\n }\n return resolved;\n }\n const manifestUrl = new URL('__vitecache/paths.json', base);\n const response = await fetch(manifestUrl, { cache: 'no-store' });\n if (!response.ok || !response.headers.get('content-type')?.includes('application/json')) {\n throw new Error('URL dependencies require the vitecache Vite plugin during development and build. Missing production URL mapping.');\n }\n const data: unknown = await response.json();\n const manifest = record(data);\n const mapping = paths(manifest.paths);\n if (typeof manifest.root !== 'string' || !/^(?:\\.\\.\\/)+$/.test(manifest.root)) throw new Error('Invalid vitecache production mapping root.');\n const projectRoot = new URL(manifest.root, manifestUrl);\n const mapped = Object.fromEntries(Object.entries(mapping).map(([url, path]) => [new URL(url, projectRoot).pathname, path]));\n const resolved = locators.map(([name, locator]): [string, ResolvedDependency] => {\n if (locator.url === undefined) return [name, locator];\n const pathname = new URL(locator.url).pathname;\n const path = mapped[pathname];\n // The build maps every file it deployed, so an unmapped URL has no backing file, as the plugin reports in development.\n if (path === undefined) throw new MissingDependencyError(`No vitecache source file mapping for URL: ${pathname}`);\n return [name, { path }];\n });\n return resolvedDependencies(Object.fromEntries(resolved));\n },\n async snapshot(dependencies) {\n if (!await plugin()) return { status: 'unavailable' };\n const response = await fetch(new URL('__vitecache/snapshot', base), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ dependencies }),\n cache: 'no-store',\n });\n if (!response.ok) throw new Error(`vitecache dependency validation failed: HTTP ${response.status}`);\n const data: unknown = await response.json();\n if (typeof data === 'object' && data !== null && 'status' in data && data.status === 'missing') return { status: 'missing' };\n if (typeof data !== 'object' || data === null || !('status' in data) || data.status !== 'available' || !('hashes' in data)) {\n throw new TypeError('Invalid vitecache dependency response.');\n }\n return { status: 'available', hashes: hashes(data.hashes) };\n },\n async load(key, validate) {\n const response = await fetch(new URL(`${key}.bin`, base), { cache: 'no-store' });\n // The plugin answers misses with 204 so they stay out of the console; static hosts answer 404.\n if (response.status === 204 || response.status === 404) return undefined;\n if (!response.ok) throw new Error(`vitecache load failed: HTTP ${response.status}`);\n const contentType = response.headers.get('content-type')?.split(';')[0]?.trim();\n if (contentType === 'text/html') return undefined;\n if (contentType !== 'application/octet-stream') throw new Error(`Unexpected vitecache content type: ${contentType ?? 'missing'}`);\n const bytes = new Uint8Array(await response.arrayBuffer());\n if (!validate) return { bytes };\n const header = response.headers.get('X-Vitecache-Dependencies');\n if (header === null) throw new Error('Missing vitecache dependency metadata.');\n const value: unknown = JSON.parse(decodeURIComponent(header));\n return { bytes, hashes: hashes(value) };\n },\n async save(key, value, validation, expected, saved) {\n // Metadata travels in the body: headers are capped near 16 KiB, and saved dependencies can be many.\n const metadata = new TextEncoder().encode(JSON.stringify({ dependencies: validation, hashes: expected, saved }));\n const body = new Uint8Array(4 + metadata.byteLength + value.byteLength);\n new DataView(body.buffer).setUint32(0, metadata.byteLength);\n body.set(metadata, 4);\n body.set(value, 4 + metadata.byteLength);\n const response = await fetch(new URL(`${key}.bin`, base), {\n method: 'PUT',\n headers: { 'Content-Type': 'application/octet-stream' },\n body,\n });\n await rejectMissing(response);\n if (!response.ok) throw new Error(`vitecache save failed: HTTP ${response.status}`);\n },\n };\n}\n\n/** A persistent binary computation result. Pass a browser basePath matching the public cache URL, including any Vite base. */\nexport class CacheEntry extends Entry {\n constructor(options: CacheEntryOptions) {\n super(options, createBackend(options));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAA4C,MAAM,YAAY,CAAC;AAG3E,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;AAEjC,+FAA+F;AAC/F,MAAM,OAAO,UAAU;IACZ,QAAQ,CAAS;IACjB,IAAI,CAAM;IAEnB,YAAY,OAA0B;QACpC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;QAChC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,IAAI,cAAc,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC1G,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACnD,CAAC;IAED,8DAA8D;IAC9D,KAAK,CAAC,IAAI;QACR,yFAAyF;QACzF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;QAC/D,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,SAAS,CAAC;QAC9C,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QACpF,yEAAyE;QACzE,IAAI,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC;YAAE,OAAO,SAAS,CAAC;QACpF,OAAO,IAAI,UAAU,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,0GAA0G;IAC1G,KAAK,CAAC,IAAI,CAAC,MAAkB,EAAE,QAAsB;QACnD,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO;QACtC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC1B,OAAO,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,QAAQ,uDAAuD,CAAC,CAAC;IACnG,CAAC;CACF","sourcesContent":["import { key, type CacheEntryOptions, type SaveOptions } from './entry.js';\nexport type { CacheEntryOptions, Dependency, Input, JsonValue, SaveOptions } from './entry.js';\n\nconst warned = new Set<string>();\n\n/** A cached result, read from the cache directory the app serves. Only Node writes entries. */\nexport class CacheEntry {\n readonly #version: string;\n readonly #url: URL;\n\n constructor(options: CacheEntryOptions) {\n this.#version = options.version;\n const base = new URL((options.basePath ?? '/_vitecache/').replace(/\\/?$/, '/'), globalThis.location.href);\n this.#url = new URL(`${key(options)}.bin`, base);\n }\n\n /** The saved bytes, or undefined when the app serves none. */\n async load(): Promise<Uint8Array | undefined> {\n // An entry keeps its name when its bytes change, so the HTTP cache must revalidate them.\n const response = await fetch(this.#url, { cache: 'no-cache' });\n if (response.status === 404) return undefined;\n if (!response.ok) throw new Error(`vitecache load failed: HTTP ${response.status}`);\n // Vite's dev server and SPA hosts answer a missing file with index.html.\n if (response.headers.get('content-type')?.startsWith('text/html')) return undefined;\n return new Uint8Array(await response.arrayBuffer());\n }\n\n /** Writes nothing, since browsers only read. Warns once per version that its result was computed here. */\n async save(_value: Uint8Array, _options?: SaveOptions): Promise<void> {\n if (warned.has(this.#version)) return;\n warned.add(this.#version);\n console.warn(`vitecache: ${this.#version} was computed in the browser, which does not save it.`);\n }\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { JsonValue } from './entry.js';
|
|
2
|
+
/** A `loadAsync(...args)` of a cached loader that discovery found, which warming repeats on a `new` loader in Node. */
|
|
3
|
+
export interface Load {
|
|
4
|
+
importer: string;
|
|
5
|
+
/** Where in the importer the load is, as `line:column`. */
|
|
6
|
+
position: string;
|
|
7
|
+
source: string;
|
|
8
|
+
name: string;
|
|
9
|
+
args: JsonValue[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The cached loader loads in a module: `new Loader(...).loadAsync(...)`, or `loadAsync(...)` on a variable initialized
|
|
13
|
+
* with `new Loader(...)`. Any other use of a loader or such a variable fails, since warming could not repeat it.
|
|
14
|
+
*/
|
|
15
|
+
export declare function discover(code: string, importer: string): Load[];
|
package/dist/discover.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import JSON5 from 'json5';
|
|
2
|
+
import { parseSync, Visitor } from 'vite';
|
|
3
|
+
/**
|
|
4
|
+
* The convention that marks a class as a cached loader: its loadAsync computes a result once and saves it. Warming
|
|
5
|
+
* constructs it without arguments, so only loadAsync's arguments may select the result.
|
|
6
|
+
*/
|
|
7
|
+
const loader = /^Cached\w*Loader$/;
|
|
8
|
+
/**
|
|
9
|
+
* The cached loader loads in a module: `new Loader(...).loadAsync(...)`, or `loadAsync(...)` on a variable initialized
|
|
10
|
+
* with `new Loader(...)`. Any other use of a loader or such a variable fails, since warming could not repeat it.
|
|
11
|
+
*/
|
|
12
|
+
export function discover(code, importer) {
|
|
13
|
+
const at = (offset) => {
|
|
14
|
+
const lines = code.slice(0, offset).split('\n');
|
|
15
|
+
return `${lines.length}:${(lines.at(-1)?.length ?? 0) + 1}`;
|
|
16
|
+
};
|
|
17
|
+
const fail = (message, offset) => {
|
|
18
|
+
throw new Error(`${message} at ${importer}:${at(offset)}`);
|
|
19
|
+
};
|
|
20
|
+
const { program, errors } = parseSync(importer, code);
|
|
21
|
+
if (errors[0])
|
|
22
|
+
fail(errors[0].message, errors[0].labels[0]?.start ?? 0);
|
|
23
|
+
const imports = new Map();
|
|
24
|
+
const namespaces = new Map();
|
|
25
|
+
for (const statement of program.body) {
|
|
26
|
+
if (statement.type !== 'ImportDeclaration')
|
|
27
|
+
continue;
|
|
28
|
+
for (const specifier of statement.specifiers) {
|
|
29
|
+
if (specifier.type === 'ImportNamespaceSpecifier')
|
|
30
|
+
namespaces.set(specifier.local.name, statement.source.value);
|
|
31
|
+
if (specifier.type !== 'ImportSpecifier')
|
|
32
|
+
continue;
|
|
33
|
+
const name = specifier.imported.type === 'Literal' ? specifier.imported.value : specifier.imported.name;
|
|
34
|
+
if (loader.test(name))
|
|
35
|
+
imports.set(specifier.local.name, { source: statement.source.value, name });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Nodes that name a loader or loader variable without using its value. */
|
|
39
|
+
const allowed = new Set();
|
|
40
|
+
// JSON5 is the literal subset of JavaScript: unquoted keys, single quotes, signed numbers.
|
|
41
|
+
const literal = (node) => {
|
|
42
|
+
try {
|
|
43
|
+
return JSON5.parse(code.slice(node.start, node.end));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return fail('Cached loader loadAsync arguments must be literal JSON', node.start);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const target = (node) => {
|
|
50
|
+
if (node.type === 'Identifier')
|
|
51
|
+
return imports.get(node.name);
|
|
52
|
+
if (node.type !== 'MemberExpression' || node.computed || node.object.type !== 'Identifier')
|
|
53
|
+
return undefined;
|
|
54
|
+
const source = namespaces.get(node.object.name);
|
|
55
|
+
return source !== undefined && loader.test(node.property.name) ? { source, name: node.property.name } : undefined;
|
|
56
|
+
};
|
|
57
|
+
const created = (node) => {
|
|
58
|
+
const loaded = target(node.callee);
|
|
59
|
+
if (loaded)
|
|
60
|
+
allowed.add(node.callee);
|
|
61
|
+
return loaded;
|
|
62
|
+
};
|
|
63
|
+
// A first pass, since a variable can be used above its declaration, as in a function declared earlier.
|
|
64
|
+
const instances = new Map();
|
|
65
|
+
new Visitor({
|
|
66
|
+
VariableDeclarator(node) {
|
|
67
|
+
if (node.id.type !== 'Identifier' || node.init?.type !== 'NewExpression')
|
|
68
|
+
return;
|
|
69
|
+
const instance = created(node.init);
|
|
70
|
+
if (!instance)
|
|
71
|
+
return;
|
|
72
|
+
instances.set(node.id.name, instance);
|
|
73
|
+
allowed.add(node.id);
|
|
74
|
+
},
|
|
75
|
+
}).visit(program);
|
|
76
|
+
const member = (node) => {
|
|
77
|
+
if (!node.computed)
|
|
78
|
+
allowed.add(node.key);
|
|
79
|
+
};
|
|
80
|
+
const misused = (node) => fail('Use cached loaders only as new Loader().loadAsync(), or a variable of one', node.start);
|
|
81
|
+
const loads = [];
|
|
82
|
+
new Visitor({
|
|
83
|
+
ImportSpecifier: (node) => allowed.add(node.local).add(node.imported),
|
|
84
|
+
// Loads through an exported loader variable happen in other modules, out of discovery's reach.
|
|
85
|
+
ExportSpecifier(node) {
|
|
86
|
+
if (node.local.type === 'Identifier' && instances.has(node.local.name))
|
|
87
|
+
misused(node);
|
|
88
|
+
allowed.add(node.local).add(node.exported);
|
|
89
|
+
},
|
|
90
|
+
ExportNamedDeclaration(node) {
|
|
91
|
+
if (node.declaration?.type !== 'VariableDeclaration')
|
|
92
|
+
return;
|
|
93
|
+
for (const { id } of node.declaration.declarations)
|
|
94
|
+
if (id.type === 'Identifier' && instances.has(id.name))
|
|
95
|
+
misused(id);
|
|
96
|
+
},
|
|
97
|
+
Property(node) {
|
|
98
|
+
if (!node.shorthand)
|
|
99
|
+
member(node);
|
|
100
|
+
},
|
|
101
|
+
MethodDefinition: member,
|
|
102
|
+
PropertyDefinition: member,
|
|
103
|
+
CallExpression(node) {
|
|
104
|
+
const { callee } = node;
|
|
105
|
+
if (callee.type !== 'MemberExpression' || callee.computed || callee.property.name !== 'loadAsync')
|
|
106
|
+
return;
|
|
107
|
+
const { object } = callee;
|
|
108
|
+
const instance = object.type === 'NewExpression'
|
|
109
|
+
? created(object)
|
|
110
|
+
: object.type === 'Identifier'
|
|
111
|
+
? instances.get(object.name)
|
|
112
|
+
: undefined;
|
|
113
|
+
if (!instance)
|
|
114
|
+
return;
|
|
115
|
+
allowed.add(object);
|
|
116
|
+
loads.push({ importer, position: at(node.start), ...instance, args: node.arguments.map(literal) });
|
|
117
|
+
},
|
|
118
|
+
MemberExpression(node) {
|
|
119
|
+
if (!node.computed)
|
|
120
|
+
allowed.add(node.property);
|
|
121
|
+
if (target(node) && !allowed.has(node))
|
|
122
|
+
misused(node);
|
|
123
|
+
},
|
|
124
|
+
Identifier(node) {
|
|
125
|
+
if ((imports.has(node.name) || instances.has(node.name)) && !allowed.has(node))
|
|
126
|
+
misused(node);
|
|
127
|
+
},
|
|
128
|
+
}).visit(program);
|
|
129
|
+
return loads;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=discover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,OAAO,EAAe,MAAM,MAAM,CAAC;AAavD;;;GAGG;AACH,MAAM,MAAM,GAAG,mBAAmB,CAAC;AAEnC;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,QAAgB;IACrD,MAAM,EAAE,GAAG,CAAC,MAAc,EAAE,EAAE;QAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChD,OAAO,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAC9D,CAAC,CAAC;IACF,MAAM,IAAI,GAAG,CAAC,OAAe,EAAE,MAAc,EAAS,EAAE;QACtD,MAAM,IAAI,KAAK,CAAC,GAAG,OAAO,OAAO,QAAQ,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC;IACF,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACtD,IAAI,MAAM,CAAC,CAAC,CAAC;QAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,IAAI,GAAG,EAA4C,CAAC;IACpE,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC7C,KAAK,MAAM,SAAS,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACrC,IAAI,SAAS,CAAC,IAAI,KAAK,mBAAmB;YAAE,SAAS;QACrD,KAAK,MAAM,SAAS,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC;YAC7C,IAAI,SAAS,CAAC,IAAI,KAAK,0BAA0B;gBAAE,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAChH,IAAI,SAAS,CAAC,IAAI,KAAK,iBAAiB;gBAAE,SAAS;YACnD,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;YACxG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACrG,CAAC;IACH,CAAC;IACD,2EAA2E;IAC3E,MAAM,OAAO,GAAG,IAAI,GAAG,EAAe,CAAC;IACvC,2FAA2F;IAC3F,MAAM,OAAO,GAAG,CAAC,IAAqB,EAAE,EAAE;QACxC,IAAI,CAAC;YACH,OAAO,KAAK,CAAC,KAAK,CAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAClE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC,wDAAwD,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACpF,CAAC;IACH,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,CAAC,IAAiB,EAAE,EAAE;QACnC,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY;YAAE,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9D,IAAI,IAAI,CAAC,IAAI,KAAK,kBAAkB,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY;YAAE,OAAO,SAAS,CAAC;QAC7G,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChD,OAAO,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IACpH,CAAC,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,IAA0B,EAAE,EAAE;QAC7C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,MAAM;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrC,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;IACF,uGAAuG;IACvG,MAAM,SAAS,GAAG,IAAI,GAAG,EAA4C,CAAC;IACtE,IAAI,OAAO,CAAC;QACV,kBAAkB,CAAC,IAAI;YACrB,IAAI,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,YAAY,IAAI,IAAI,CAAC,IAAI,EAAE,IAAI,KAAK,eAAe;gBAAE,OAAO;YACjF,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpC,IAAI,CAAC,QAAQ;gBAAE,OAAO;YACtB,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YACtC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACvB,CAAC;KACF,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAClB,MAAM,MAAM,GAAG,CAAC,IAA6C,EAAE,EAAE;QAC/D,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5C,CAAC,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,IAAiB,EAAE,EAAE,CACpC,IAAI,CAAC,2EAA2E,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAChG,MAAM,KAAK,GAAW,EAAE,CAAC;IACzB,IAAI,OAAO,CAAC;QACV,eAAe,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;QACrE,+FAA+F;QAC/F,eAAe,CAAC,IAAI;YAClB,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,YAAY,IAAI,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,IAAI,CAAC,CAAC;YACtF,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7C,CAAC;QACD,sBAAsB,CAAC,IAAI;YACzB,IAAI,IAAI,CAAC,WAAW,EAAE,IAAI,KAAK,qBAAqB;gBAAE,OAAO;YAC7D,KAAK,MAAM,EAAE,EAAE,EAAE,IAAI,IAAI,CAAC,WAAW,CAAC,YAAY;gBAChD,IAAI,EAAE,CAAC,IAAI,KAAK,YAAY,IAAI,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC;oBAAE,OAAO,CAAC,EAAE,CAAC,CAAC;QACxE,CAAC;QACD,QAAQ,CAAC,IAAI;YACX,IAAI,CAAC,IAAI,CAAC,SAAS;gBAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC;QACD,gBAAgB,EAAE,MAAM;QACxB,kBAAkB,EAAE,MAAM;QAC1B,cAAc,CAAC,IAAI;YACjB,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;YACxB,IAAI,MAAM,CAAC,IAAI,KAAK,kBAAkB,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,WAAW;gBAAE,OAAO;YAC1G,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;YAC1B,MAAM,QAAQ,GACZ,MAAM,CAAC,IAAI,KAAK,eAAe;gBAC7B,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;gBACjB,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY;oBAC5B,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;oBAC5B,CAAC,CAAC,SAAS,CAAC;YAClB,IAAI,CAAC,QAAQ;gBAAE,OAAO;YACtB,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACpB,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACrG,CAAC;QACD,gBAAgB,CAAC,IAAI;YACnB,IAAI,CAAC,IAAI,CAAC,QAAQ;gBAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC/C,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QACxD,CAAC;QACD,UAAU,CAAC,IAAI;YACb,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAChG,CAAC;KACF,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAClB,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["import JSON5 from 'json5';\nimport { parseSync, Visitor, type ESTree } from 'vite';\nimport type { JsonValue } from './entry.js';\n\n/** A `loadAsync(...args)` of a cached loader that discovery found, which warming repeats on a `new` loader in Node. */\nexport interface Load {\n importer: string;\n /** Where in the importer the load is, as `line:column`. */\n position: string;\n source: string;\n name: string;\n args: JsonValue[];\n}\n\n/**\n * The convention that marks a class as a cached loader: its loadAsync computes a result once and saves it. Warming\n * constructs it without arguments, so only loadAsync's arguments may select the result.\n */\nconst loader = /^Cached\\w*Loader$/;\n\n/**\n * The cached loader loads in a module: `new Loader(...).loadAsync(...)`, or `loadAsync(...)` on a variable initialized\n * with `new Loader(...)`. Any other use of a loader or such a variable fails, since warming could not repeat it.\n */\nexport function discover(code: string, importer: string): Load[] {\n const at = (offset: number) => {\n const lines = code.slice(0, offset).split('\\n');\n return `${lines.length}:${(lines.at(-1)?.length ?? 0) + 1}`;\n };\n const fail = (message: string, offset: number): never => {\n throw new Error(`${message} at ${importer}:${at(offset)}`);\n };\n const { program, errors } = parseSync(importer, code);\n if (errors[0]) fail(errors[0].message, errors[0].labels[0]?.start ?? 0);\n const imports = new Map<string, { source: string; name: string }>();\n const namespaces = new Map<string, string>();\n for (const statement of program.body) {\n if (statement.type !== 'ImportDeclaration') continue;\n for (const specifier of statement.specifiers) {\n if (specifier.type === 'ImportNamespaceSpecifier') namespaces.set(specifier.local.name, statement.source.value);\n if (specifier.type !== 'ImportSpecifier') continue;\n const name = specifier.imported.type === 'Literal' ? specifier.imported.value : specifier.imported.name;\n if (loader.test(name)) imports.set(specifier.local.name, { source: statement.source.value, name });\n }\n }\n /** Nodes that name a loader or loader variable without using its value. */\n const allowed = new Set<ESTree.Node>();\n // JSON5 is the literal subset of JavaScript: unquoted keys, single quotes, signed numbers.\n const literal = (node: ESTree.Argument) => {\n try {\n return JSON5.parse<JsonValue>(code.slice(node.start, node.end));\n } catch {\n return fail('Cached loader loadAsync arguments must be literal JSON', node.start);\n }\n };\n const target = (node: ESTree.Node) => {\n if (node.type === 'Identifier') return imports.get(node.name);\n if (node.type !== 'MemberExpression' || node.computed || node.object.type !== 'Identifier') return undefined;\n const source = namespaces.get(node.object.name);\n return source !== undefined && loader.test(node.property.name) ? { source, name: node.property.name } : undefined;\n };\n const created = (node: ESTree.NewExpression) => {\n const loaded = target(node.callee);\n if (loaded) allowed.add(node.callee);\n return loaded;\n };\n // A first pass, since a variable can be used above its declaration, as in a function declared earlier.\n const instances = new Map<string, { source: string; name: string }>();\n new Visitor({\n VariableDeclarator(node) {\n if (node.id.type !== 'Identifier' || node.init?.type !== 'NewExpression') return;\n const instance = created(node.init);\n if (!instance) return;\n instances.set(node.id.name, instance);\n allowed.add(node.id);\n },\n }).visit(program);\n const member = (node: { computed: boolean; key: ESTree.Node }) => {\n if (!node.computed) allowed.add(node.key);\n };\n const misused = (node: ESTree.Node) =>\n fail('Use cached loaders only as new Loader().loadAsync(), or a variable of one', node.start);\n const loads: Load[] = [];\n new Visitor({\n ImportSpecifier: (node) => allowed.add(node.local).add(node.imported),\n // Loads through an exported loader variable happen in other modules, out of discovery's reach.\n ExportSpecifier(node) {\n if (node.local.type === 'Identifier' && instances.has(node.local.name)) misused(node);\n allowed.add(node.local).add(node.exported);\n },\n ExportNamedDeclaration(node) {\n if (node.declaration?.type !== 'VariableDeclaration') return;\n for (const { id } of node.declaration.declarations)\n if (id.type === 'Identifier' && instances.has(id.name)) misused(id);\n },\n Property(node) {\n if (!node.shorthand) member(node);\n },\n MethodDefinition: member,\n PropertyDefinition: member,\n CallExpression(node) {\n const { callee } = node;\n if (callee.type !== 'MemberExpression' || callee.computed || callee.property.name !== 'loadAsync') return;\n const { object } = callee;\n const instance =\n object.type === 'NewExpression'\n ? created(object)\n : object.type === 'Identifier'\n ? instances.get(object.name)\n : undefined;\n if (!instance) return;\n allowed.add(object);\n loads.push({ importer, position: at(node.start), ...instance, args: node.arguments.map(literal) });\n },\n MemberExpression(node) {\n if (!node.computed) allowed.add(node.property);\n if (target(node) && !allowed.has(node)) misused(node);\n },\n Identifier(node) {\n if ((imports.has(node.name) || instances.has(node.name)) && !allowed.has(node)) misused(node);\n },\n }).visit(program);\n return loads;\n}\n"]}
|
package/dist/disk.d.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import type { CacheEntryOptions } from './types.js';
|
|
2
1
|
export declare function missing(error: unknown): boolean;
|
|
3
|
-
|
|
4
|
-
export declare function
|
|
5
|
-
root: string;
|
|
6
|
-
directory: string;
|
|
7
|
-
};
|
|
8
|
-
export declare function ignore(root: string, directory: string): Promise<void>;
|
|
9
|
-
export declare function load(path: string): Promise<Uint8Array | undefined>;
|
|
10
|
-
export declare function save(path: string, value: Uint8Array, beforeCommit?: () => Promise<void>): Promise<void>;
|
|
2
|
+
/** The file's bytes, or undefined when it does not exist. */
|
|
3
|
+
export declare function read(path: string): Promise<Uint8Array | undefined>;
|
package/dist/disk.js
CHANGED
|
@@ -1,71 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { appendFile, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
4
2
|
export function missing(error) {
|
|
5
|
-
return error instanceof Error && 'code' in error && error.code === 'ENOENT';
|
|
3
|
+
return error instanceof Error && 'code' in error && (error.code === 'ENOENT' || error.code === 'ENOTDIR');
|
|
6
4
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
export function location(options) {
|
|
11
|
-
const root = resolve(options.root ?? process.cwd());
|
|
12
|
-
return { root, directory: resolve(root, options.basePath ?? 'public/_vitecache') };
|
|
13
|
-
}
|
|
14
|
-
const pending = new Map();
|
|
15
|
-
export function ignore(root, directory) {
|
|
16
|
-
const key = `${root}\0${directory}`;
|
|
17
|
-
const existing = pending.get(key);
|
|
18
|
-
if (existing)
|
|
19
|
-
return existing;
|
|
20
|
-
const result = updateIgnore(root, directory).finally(() => pending.delete(key));
|
|
21
|
-
pending.set(key, result);
|
|
22
|
-
return result;
|
|
23
|
-
}
|
|
24
|
-
async function updateIgnore(root, directory) {
|
|
25
|
-
const path = relative(root, directory);
|
|
26
|
-
if (!path || path === '..' || path.startsWith(`..${sep}`) || isAbsolute(path))
|
|
27
|
-
return;
|
|
28
|
-
const entry = `${path.split(sep).join('/').replace(/[!*?[\]# ]/g, '\\$&')}/*`;
|
|
29
|
-
const file = join(root, '.gitignore');
|
|
30
|
-
await mkdir(root, { recursive: true });
|
|
31
|
-
let contents = '';
|
|
32
|
-
try {
|
|
33
|
-
contents = await readFile(file, 'utf8');
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
if (!missing(error))
|
|
37
|
-
throw error;
|
|
38
|
-
}
|
|
39
|
-
if (contents.split(/\r?\n/).includes(entry))
|
|
40
|
-
return;
|
|
41
|
-
await appendFile(file, `${contents && !contents.endsWith('\n') ? '\n' : ''}${entry}\n`);
|
|
42
|
-
}
|
|
43
|
-
export async function load(path) {
|
|
5
|
+
/** The file's bytes, or undefined when it does not exist. */
|
|
6
|
+
export async function read(path) {
|
|
44
7
|
try {
|
|
45
|
-
|
|
8
|
+
// A plain Uint8Array owning its buffer, as a browser's response gives, rather than a Buffer.
|
|
9
|
+
return new Uint8Array(await readFile(path));
|
|
46
10
|
}
|
|
47
11
|
catch (error) {
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
export async function save(path, value, beforeCommit) {
|
|
54
|
-
await mkdir(dirname(path), { recursive: true });
|
|
55
|
-
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
56
|
-
try {
|
|
57
|
-
await writeFile(temporary, value, { flag: 'wx' });
|
|
58
|
-
await beforeCommit?.();
|
|
59
|
-
await rename(temporary, path);
|
|
60
|
-
}
|
|
61
|
-
finally {
|
|
62
|
-
try {
|
|
63
|
-
await unlink(temporary);
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
if (!missing(error))
|
|
67
|
-
throw error;
|
|
68
|
-
}
|
|
12
|
+
if (missing(error))
|
|
13
|
+
return undefined;
|
|
14
|
+
throw error;
|
|
69
15
|
}
|
|
70
16
|
}
|
|
71
17
|
//# sourceMappingURL=disk.js.map
|
package/dist/disk.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"disk.js","sourceRoot":"","sources":["../src/disk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"disk.js","sourceRoot":"","sources":["../src/disk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,OAAO,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;AAC5G,CAAC;AAED,6DAA6D;AAC7D,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAY;IACrC,IAAI,CAAC;QACH,6FAA6F;QAC7F,OAAO,IAAI,UAAU,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QACrC,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["import { readFile } from 'node:fs/promises';\n\nexport function missing(error: unknown): boolean {\n return error instanceof Error && 'code' in error && (error.code === 'ENOENT' || error.code === 'ENOTDIR');\n}\n\n/** The file's bytes, or undefined when it does not exist. */\nexport async function read(path: string): Promise<Uint8Array | undefined> {\n try {\n // A plain Uint8Array owning its buffer, as a browser's response gives, rather than a Buffer.\n return new Uint8Array(await readFile(path));\n } catch (error) {\n if (missing(error)) return undefined;\n throw error;\n }\n}\n"]}
|