@pitlane/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/CHANGELOG.md +18 -0
- package/dist/manifest.d.mts +1 -0
- package/dist/manifest.mjs +1 -0
- package/dist/vite-plugin.d.mts +32 -0
- package/dist/vite-plugin.mjs +485 -42
- package/package.json +69 -71
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @pitlane/assets
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f14d251: `assets()` accepts `allowFiles`, `allowPackages`, and `denyFiles`, the browser boundary options of `remix/assets`, and reads them the same way. Setting `allowFiles` turns the boundary on: `vite build` fails, listing each file and its importer, when a file outside it would reach the client output, including files that server code links and the build copies there. `vite dev` fails the browser transform of such a module and refuses to serve such a file. Without `allowFiles` nothing changes. With `remix()`, pass the options as `remix({ assets: { allowFiles, … } })`. Upgrade if you deploy the same app to the `remix/assets` Node server and to a bundled host such as Oxygen or Cloudflare, so a file the Node server refuses no longer ships silently from the build.
|
|
8
|
+
- f14d251: `@pitlane/assets/vite-plugin` exports `ASSETS_MANIFEST_FILE`, the name of the manifest module `vite build` writes at the root of each server environment's output directory. Server bundles import that file by a relative path, so a host that deploys a single module, such as Shopify Oxygen, has to fold it into the bundle in a later build. Use the constant there instead of hardcoding `__pitlane_assets_manifest.js`.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- The API reference and installed declarations now document `assets()` and every `AssetsPluginOptions` member, and the default `@pitlane/assets/manifest` export. No behavior changed.
|
|
13
|
+
- d7fcdd4: The development manifest no longer rebuilds from scratch. `assets()` under `vite dev` now keeps the import edges it recorded for every module and, after an edit, transforms only the changed file before answering the next request; modules the server never executed, such as lazy routes, are no longer transformed again on every invalidation. Browser modules it discovers are transformed through Vite's own request pipeline, so the browser's first load reads the cache instead of transforming them a second time.
|
|
14
|
+
|
|
15
|
+
On an app with about 1,900 server modules and 400 browser modules, an edit that previously held the next request for 20–45 seconds now answers in under 2 seconds, the same as the previous `@pitlane/dev` integration, and the first manifest costs about a third less. The dev manifest's contents are unchanged.
|
|
16
|
+
|
|
17
|
+
- 92ce0a2: `vite build` no longer publishes server-only files into the client output. The copy from each server build into the client output directory took every file the server bundle emitted, so server sourcemaps, `.dev.vars` and `wrangler.json` from `@cloudflare/vite-plugin`, `oxygen.json` from MiniOxygen, and anything else a plugin wrote beside the server bundle ended up in the publicly served directory. Only the stylesheets the server code imports, the fonts and images they reference, and the files the server code imports are copied now, and the server's half of the asset manifest names only those files. Upgrade if you deploy the client output of an app built with server sourcemaps or a platform plugin.
|
|
18
|
+
- 8e38080: The development manifest no longer stays out of date after a build that changes kept interrupting. Under `vite dev`, `assets()` builds the manifest again when a module it reads is invalidated mid-build, and gives up after three builds so a plugin that invalidates modules on every transform cannot hold the request forever. Vite then cached the manifest it gave up on, and it stayed cached, missing the last change's stylesheets and browser entries, until another edit invalidated it. That was most likely on a cold start, where Vite's dependency optimizer re-runs and invalidates the browser module graph. The next import of `@pitlane/assets/manifest` now builds it again, and a build that completes uninterrupted is cached as before.
|
|
19
|
+
- d8670f8: The development manifest no longer keeps what a module imported, or which browser entries it named, before an edit. Under `vite dev`, `assets()` can transform one module more than once at the same time: the manifest's discovery and the module runner each ask for it, or an edit invalidates the module mid-transform. If a transform of the old code finished last, the manifest kept that code's stylesheets and literal `getScriptEntry()` and `getHref()` inputs until the module changed again, including in the first manifest a dev server built. A transform whose module was invalidated after its request began now records nothing, and a manifest being built when that happens is built again.
|
|
20
|
+
|
|
3
21
|
## 0.1.0
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
package/dist/manifest.d.mts
CHANGED
|
@@ -12,6 +12,7 @@ import { o as AssetsManifest } from "./types-DlTsImzF.mjs";
|
|
|
12
12
|
*
|
|
13
13
|
* @module @pitlane/assets/manifest
|
|
14
14
|
*/
|
|
15
|
+
/** `{ mode: "unavailable" }`, until `assets()` or an alias replaces this module. */
|
|
15
16
|
declare let manifest: AssetsManifest;
|
|
16
17
|
//#endregion
|
|
17
18
|
export { manifest as default };
|
package/dist/manifest.mjs
CHANGED
package/dist/vite-plugin.d.mts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { PluginOption } from "vite";
|
|
2
|
+
//#region src/vite/state.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The manifest module a build writes at the root of each server environment's
|
|
5
|
+
* output directory. The server chunks that read the manifest import this file
|
|
6
|
+
* by a relative path, so a host that deploys a single module must bundle it in.
|
|
7
|
+
*/
|
|
8
|
+
export declare const ASSETS_MANIFEST_FILE = "__pitlane_assets_manifest.js";
|
|
9
|
+
//#endregion
|
|
2
10
|
//#region src/vite-plugin.d.ts
|
|
11
|
+
/** Options for {@link assets}. `remix()` from `@pitlane/vite-plugin-remix` accepts them as `remix({ assets })`. */
|
|
3
12
|
export interface AssetsPluginOptions {
|
|
4
13
|
/** Extra source keys to make available as client entries or assets. */
|
|
5
14
|
include?: string[];
|
|
@@ -7,6 +16,21 @@ export interface AssetsPluginOptions {
|
|
|
7
16
|
serverEnvironments?: string[];
|
|
8
17
|
/** Override Vite's native setting. With neither setting present, maps are disabled. */
|
|
9
18
|
chunkImportMap?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Files and globs, relative to the Vite root, that may reach the browser.
|
|
21
|
+
* Setting it turns on the browser boundary: a build that would publish any
|
|
22
|
+
* other file fails, and `vite dev` refuses to serve one. Read as
|
|
23
|
+
* `remix/assets` reads it; a directory path matches every file below it.
|
|
24
|
+
*/
|
|
25
|
+
allowFiles?: string[];
|
|
26
|
+
/**
|
|
27
|
+
* Packages that may reach the browser, with their `dependencies` and
|
|
28
|
+
* installed `optionalDependencies`. Peer dependencies are not included.
|
|
29
|
+
* Requires `allowFiles`.
|
|
30
|
+
*/
|
|
31
|
+
allowPackages?: string[];
|
|
32
|
+
/** Files and globs refused even when `allowFiles` or `allowPackages` allows them. Requires `allowFiles`. */
|
|
33
|
+
denyFiles?: string[];
|
|
10
34
|
}
|
|
11
35
|
/** Browser entries declared by one server module's development transform. */
|
|
12
36
|
export interface AssetEntryRegistration {
|
|
@@ -22,5 +46,13 @@ export interface AssetsPluginApi {
|
|
|
22
46
|
/** Replace an owner's declarations; an empty list removes them. Builds use `emitFile` instead. */
|
|
23
47
|
setBrowserEntries(registration: AssetEntryRegistration): void;
|
|
24
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* The Vite plugins that give `@pitlane/assets` its manifest: they serve
|
|
51
|
+
* browser entries during `vite dev`, emit them in a build, and replace
|
|
52
|
+
* `@pitlane/assets/manifest` with the manifest of the current dev server or
|
|
53
|
+
* build. Setting `allowFiles` also enforces the browser boundary.
|
|
54
|
+
*
|
|
55
|
+
* @throws When `allowPackages` or `denyFiles` is set without `allowFiles`.
|
|
56
|
+
*/
|
|
25
57
|
export declare function assets(options?: AssetsPluginOptions): PluginOption;
|
|
26
58
|
//#endregion
|
package/dist/vite-plugin.mjs
CHANGED
|
@@ -1,11 +1,90 @@
|
|
|
1
1
|
import { n as quotedList, t as assetsSpecifier } from "./specifier-DLC81sXY.mjs";
|
|
2
2
|
import { t as createAssetManifest } from "./build-BbA-Zq1m.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { basename, dirname, isAbsolute, posix, relative, resolve } from "node:path";
|
|
5
|
-
import { isCSSRequest, normalizePath, parseSync } from "vite";
|
|
3
|
+
import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, posix, relative, resolve } from "node:path";
|
|
6
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { createFilter, isCSSRequest, normalizePath, parseSync } from "vite";
|
|
7
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
7
8
|
import MagicString from "magic-string";
|
|
8
|
-
import {
|
|
9
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
10
|
+
//#region src/vite/boundary-policy.ts
|
|
11
|
+
/**
|
|
12
|
+
* Builds the access check `remix/assets` applies to the files it serves.
|
|
13
|
+
* `root` is a real path; files passed to the check are real absolute paths.
|
|
14
|
+
*/
|
|
15
|
+
function createBrowserBoundary(root, options) {
|
|
16
|
+
let allowMatchers = options.allowFiles.map((pattern) => fileMatcher(root, pattern));
|
|
17
|
+
let denyMatchers = (options.denyFiles ?? []).map((pattern) => ({
|
|
18
|
+
pattern,
|
|
19
|
+
matches: fileMatcher(root, pattern)
|
|
20
|
+
}));
|
|
21
|
+
let packageRoots = allowedPackageRoots(root, (options.allowPackages ?? []).map((name) => name.trim()));
|
|
22
|
+
return (file) => {
|
|
23
|
+
if (file.includes("/node_modules/@oxc-project/runtime/")) return { allowed: true };
|
|
24
|
+
if (!(allowMatchers.some((matches) => matches(file)) || packageRoots.some((directory) => within(directory, file)))) return { allowed: false };
|
|
25
|
+
let denial = denyMatchers.find(({ matches }) => matches(file));
|
|
26
|
+
return denial ? {
|
|
27
|
+
allowed: false,
|
|
28
|
+
deniedBy: denial.pattern
|
|
29
|
+
} : { allowed: true };
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Whether `file` is `directory` or below it. */
|
|
33
|
+
function within(directory, file) {
|
|
34
|
+
return file === directory || file.startsWith(`${directory}/`);
|
|
35
|
+
}
|
|
36
|
+
function fileMatcher(root, pattern) {
|
|
37
|
+
let path = normalizePath(isAbsolute(pattern) ? pattern : join(root, pattern));
|
|
38
|
+
if (/[*?[\]{}()!+@]/.test(pattern)) return createFilter(path, null, { resolve: false });
|
|
39
|
+
if (!existsSync(path)) return (file) => file === path;
|
|
40
|
+
let real = normalizePath(realpathSync(path));
|
|
41
|
+
return statSync(real).isDirectory() ? (file) => within(real, file) : (file) => file === real;
|
|
42
|
+
}
|
|
43
|
+
/** Whether `name` is a bare or scoped package name, not a path, as `remix/assets` requires. */
|
|
44
|
+
function isPackageName(name) {
|
|
45
|
+
let scoped = name.startsWith("@");
|
|
46
|
+
let parts = (scoped ? name.slice(1) : name).split("/");
|
|
47
|
+
return parts.length === (scoped ? 2 : 1) && parts.every((part) => /^[\w.~-]+$/.test(part) && part !== "." && part !== "..");
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The real directories of each named package and of everything its
|
|
51
|
+
* `dependencies` and installed `optionalDependencies` pull in, transitively.
|
|
52
|
+
* Peer dependencies are left out, as `remix/assets` leaves them out.
|
|
53
|
+
*/
|
|
54
|
+
function allowedPackageRoots(root, names) {
|
|
55
|
+
let roots = /* @__PURE__ */ new Set();
|
|
56
|
+
let pending = names.map((name) => {
|
|
57
|
+
if (!isPackageName(name)) throw new Error(`[assets] allowPackages values must be package names. Received ${JSON.stringify(name)}.`);
|
|
58
|
+
let directory = packageDirectory(name, root);
|
|
59
|
+
if (!directory) throw new Error(`[assets] Could not resolve allowed package "${name}".`);
|
|
60
|
+
return directory;
|
|
61
|
+
});
|
|
62
|
+
while (pending.length > 0) {
|
|
63
|
+
let directory = pending.pop();
|
|
64
|
+
if (roots.has(directory)) continue;
|
|
65
|
+
roots.add(directory);
|
|
66
|
+
let manifest = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"));
|
|
67
|
+
for (let name of Object.keys(manifest.dependencies ?? {})) {
|
|
68
|
+
let dependency = packageDirectory(name, directory);
|
|
69
|
+
if (!dependency) throw new Error(`[assets] Could not resolve dependency "${name}" of allowed package ${directory}.`);
|
|
70
|
+
pending.push(dependency);
|
|
71
|
+
}
|
|
72
|
+
for (let name of Object.keys(manifest.optionalDependencies ?? {})) {
|
|
73
|
+
let dependency = packageDirectory(name, directory);
|
|
74
|
+
if (dependency) pending.push(dependency);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return [...roots];
|
|
78
|
+
}
|
|
79
|
+
/** The real directory Node's lookup finds for package `name` from `from`, or `undefined`. */
|
|
80
|
+
function packageDirectory(name, from) {
|
|
81
|
+
for (let directory = from;; directory = dirname(directory)) {
|
|
82
|
+
let candidate = join(directory, "node_modules", name);
|
|
83
|
+
if (existsSync(join(candidate, "package.json"))) return normalizePath(realpathSync(candidate));
|
|
84
|
+
if (dirname(directory) === directory) return void 0;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
9
88
|
//#region src/vite/entries.ts
|
|
10
89
|
function sourceKey(root, id) {
|
|
11
90
|
let file = id.split("?")[0].split("#")[0];
|
|
@@ -22,6 +101,217 @@ function fileModule(id) {
|
|
|
22
101
|
return !id.startsWith("\0") && isAbsolute(id.split("?")[0]);
|
|
23
102
|
}
|
|
24
103
|
//#endregion
|
|
104
|
+
//#region src/vite/folded-files.ts
|
|
105
|
+
/** `context`, with `addWatchFile` reporting each file to `observe` before Vite records it. */
|
|
106
|
+
function watching(context, observe) {
|
|
107
|
+
return new Proxy(context, { get(target, property) {
|
|
108
|
+
if (property === "addWatchFile") return (file) => {
|
|
109
|
+
observe(file);
|
|
110
|
+
target.addWatchFile(file);
|
|
111
|
+
};
|
|
112
|
+
let value = Reflect.get(target, property, target);
|
|
113
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
114
|
+
} });
|
|
115
|
+
}
|
|
116
|
+
function unsupported(name) {
|
|
117
|
+
return /* @__PURE__ */ new Error(`[assets] This Vite version does not expose the ${name} hook the browser boundary reads. Use a supported Vite version.`);
|
|
118
|
+
}
|
|
119
|
+
/** Calls `observe` with every file the folding plugins report, by wrapping their hooks' contexts. */
|
|
120
|
+
function observeFoldedFiles(config, observe) {
|
|
121
|
+
let plugin = (name) => config.plugins.find((candidate) => candidate.name === name);
|
|
122
|
+
for (let name of ["vite:css", "vite:worker-import-meta-url"]) {
|
|
123
|
+
let hook = plugin(name)?.transform;
|
|
124
|
+
if (typeof hook !== "object") throw unsupported(name);
|
|
125
|
+
let handler = hook.handler;
|
|
126
|
+
hook.handler = function(code, id, options) {
|
|
127
|
+
let context = watching(this, (file) => observe(this.environment, name, id, file));
|
|
128
|
+
return handler.call(context, code, id, options);
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
let hook = plugin("vite:worker")?.load;
|
|
132
|
+
if (typeof hook !== "object") throw unsupported("vite:worker");
|
|
133
|
+
let handler = hook.handler;
|
|
134
|
+
hook.handler = function(id, options) {
|
|
135
|
+
let context = watching(this, (file) => observe(this.environment, "vite:worker", id, file));
|
|
136
|
+
return handler.call(context, id, options);
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/vite/boundary.ts
|
|
141
|
+
function describe({ file, importer, deniedBy }) {
|
|
142
|
+
let reason = deniedBy ? `matches denyFiles ${JSON.stringify(deniedBy)}` : "not in allowFiles or allowPackages";
|
|
143
|
+
return `${file}${importer ? ` (imported by ${importer})` : ""}: ${reason}`;
|
|
144
|
+
}
|
|
145
|
+
/** Query-free path of a module id or emitted file name. */
|
|
146
|
+
function filePath(id) {
|
|
147
|
+
return normalizePath(id.split(/[?#]/, 1)[0]);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* A page, or an inline script Vite proxies out of one: the boundary governs
|
|
151
|
+
* what a page loads, not the page. An HTML file a module imports is data.
|
|
152
|
+
*/
|
|
153
|
+
function htmlDocument(id) {
|
|
154
|
+
return filePath(id).endsWith(".html") && (!id.includes("?") || /[?&]html-proxy\b/.test(id));
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The server files a build copies into the client output: every stylesheet
|
|
158
|
+
* the server graph imports, except as a string or URL, and the assets its
|
|
159
|
+
* chunks link.
|
|
160
|
+
*/
|
|
161
|
+
function publishedServerFiles(bundle) {
|
|
162
|
+
let files = [];
|
|
163
|
+
for (let output of Object.values(bundle)) {
|
|
164
|
+
if (output.type !== "chunk" || !output.viteMetadata) continue;
|
|
165
|
+
for (let id of output.moduleIds) if (isCSSRequest(id) && !/[?&](?:inline|raw|url)\b/.test(id)) files.push(id);
|
|
166
|
+
for (let fileName of output.viteMetadata.importedAssets) {
|
|
167
|
+
let asset = bundle[fileName];
|
|
168
|
+
if (asset?.type === "asset") files.push(...asset.originalFileNames);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return files;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Holds the client graph and every file the build publishes for the browser
|
|
175
|
+
* to `allowFiles`, `allowPackages`, and `denyFiles`, the way `remix/assets`
|
|
176
|
+
* holds the files its asset server answers.
|
|
177
|
+
*/
|
|
178
|
+
function browserBoundary(options, serverEnvironments) {
|
|
179
|
+
let root = "";
|
|
180
|
+
let inspect;
|
|
181
|
+
let viteClient = normalizePath(dirname(fileURLToPath(import.meta.resolve("vite/dist/client/client.mjs"))));
|
|
182
|
+
/** Build module id → the files a folding plugin read into it. */
|
|
183
|
+
let foldedFiles = /* @__PURE__ */ new Map();
|
|
184
|
+
function refusal(file, importer) {
|
|
185
|
+
let verdict = inspect(file);
|
|
186
|
+
if (verdict.allowed) return;
|
|
187
|
+
return {
|
|
188
|
+
file: sourceKey(root, file),
|
|
189
|
+
importer: importer && sourceKey(root, importer),
|
|
190
|
+
deniedBy: verdict.deniedBy
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/** The real path of the file `path` names from the root, or `undefined` when none exists. */
|
|
194
|
+
function existingFile(path) {
|
|
195
|
+
let file = resolve(root, filePath(path));
|
|
196
|
+
if (!existsSync(file) || !statSync(file).isFile()) return;
|
|
197
|
+
return normalizePath(realpathSync(file));
|
|
198
|
+
}
|
|
199
|
+
/** The source file a dev request reaches, or `undefined` when Vite itself supplies it. */
|
|
200
|
+
function servedSource(environment, file) {
|
|
201
|
+
if (within(viteClient, file)) return;
|
|
202
|
+
let optimizer = environment.depsOptimizer;
|
|
203
|
+
if (!optimizer?.isOptimizedDepFile(file)) return file;
|
|
204
|
+
let { optimized, discovered } = optimizer.metadata;
|
|
205
|
+
let dependency = [...Object.values(optimized), ...Object.values(discovered)].find((info) => normalizePath(info.file) === file);
|
|
206
|
+
return dependency?.src && normalizePath(dependency.src);
|
|
207
|
+
}
|
|
208
|
+
function devRefusal(environment, file, importer) {
|
|
209
|
+
let source = servedSource(environment, file);
|
|
210
|
+
if (!source) return;
|
|
211
|
+
importer ??= [...environment.moduleGraph.getModulesByFile(file) ?? []].flatMap((module) => [...module.importers]).find((module) => module.file)?.file ?? void 0;
|
|
212
|
+
return refusal(source, importer);
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
name: "pitlane-assets-boundary",
|
|
216
|
+
sharedDuringBuild: true,
|
|
217
|
+
configResolved(config) {
|
|
218
|
+
root = config.root;
|
|
219
|
+
inspect = createBrowserBoundary(normalizePath(realpathSync(root)), options);
|
|
220
|
+
observeFoldedFiles(config, (environment, plugin, module, file) => {
|
|
221
|
+
if (plugin === "vite:css" && !isCSSRequest(file) && !config.assetsInclude(file)) return;
|
|
222
|
+
if (environment.mode === "build") {
|
|
223
|
+
let files = foldedFiles.get(module);
|
|
224
|
+
if (!files) foldedFiles.set(module, files = /* @__PURE__ */ new Set());
|
|
225
|
+
files.add(file);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (environment.name !== "client" || environment.mode !== "dev") return;
|
|
229
|
+
let source = existingFile(file);
|
|
230
|
+
let refused = source && devRefusal(environment, source, module);
|
|
231
|
+
if (refused) throw new Error(`[assets] Outside the browser boundary: ${describe(refused)}`);
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
configureServer(server) {
|
|
235
|
+
let client = server.environments.client;
|
|
236
|
+
let { publicDir, base } = server.config;
|
|
237
|
+
server.middlewares.use(((request, _response, next) => {
|
|
238
|
+
let path;
|
|
239
|
+
try {
|
|
240
|
+
path = decodeURIComponent(filePath(request.url ?? "/"));
|
|
241
|
+
} catch {
|
|
242
|
+
return next();
|
|
243
|
+
}
|
|
244
|
+
if (base !== "/" && path.startsWith(base)) path = `/${path.slice(base.length)}`;
|
|
245
|
+
if (path.startsWith("/@") && !path.startsWith("/@fs/")) return next();
|
|
246
|
+
if (publicDir && existsSync(join(publicDir, path))) return next();
|
|
247
|
+
if (htmlDocument(path)) return next();
|
|
248
|
+
let requested = join(root, path);
|
|
249
|
+
if (path.startsWith("/@fs/")) {
|
|
250
|
+
let fsPath = path.slice(5);
|
|
251
|
+
requested = /^[A-Za-z]:\//.test(fsPath) ? fsPath : `/${fsPath}`;
|
|
252
|
+
}
|
|
253
|
+
let file = existingFile(requested);
|
|
254
|
+
let refused = file && devRefusal(client, file);
|
|
255
|
+
if (!refused) return next();
|
|
256
|
+
next(/* @__PURE__ */ new Error(`[assets] Outside the browser boundary: ${describe(refused)}`));
|
|
257
|
+
}));
|
|
258
|
+
},
|
|
259
|
+
transform: {
|
|
260
|
+
order: "pre",
|
|
261
|
+
handler(_code, id) {
|
|
262
|
+
let environment = this.environment;
|
|
263
|
+
if (environment.mode !== "dev" || environment.name !== "client" || !fileModule(id)) return;
|
|
264
|
+
if (htmlDocument(id)) return;
|
|
265
|
+
let refused = devRefusal(environment, existingFile(id) ?? filePath(id));
|
|
266
|
+
if (refused) throw new Error(`[assets] Outside the browser boundary: ${describe(refused)}`);
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
generateBundle(_options, bundle) {
|
|
270
|
+
let name = this.environment.name;
|
|
271
|
+
let published = [];
|
|
272
|
+
if (name === "client") {
|
|
273
|
+
for (let id of this.getModuleIds()) {
|
|
274
|
+
if (!fileModule(id)) continue;
|
|
275
|
+
let info = this.getModuleInfo(id);
|
|
276
|
+
let importer = [...info?.importers ?? [], ...info?.dynamicImporters ?? []].find(fileModule);
|
|
277
|
+
published.push({
|
|
278
|
+
path: id,
|
|
279
|
+
importer
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
for (let output of Object.values(bundle)) {
|
|
283
|
+
if (output.type !== "asset" || output.names.some(isCSSRequest)) continue;
|
|
284
|
+
for (let path of output.originalFileNames) published.push({ path });
|
|
285
|
+
}
|
|
286
|
+
} else if (serverEnvironments.includes(name)) for (let path of publishedServerFiles(bundle)) {
|
|
287
|
+
let importer = this.getModuleInfo(path)?.importers.find(fileModule);
|
|
288
|
+
published.push({
|
|
289
|
+
path,
|
|
290
|
+
importer
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
let refusals = /* @__PURE__ */ new Map();
|
|
294
|
+
for (let { path, importer } of published) {
|
|
295
|
+
let entries = [{
|
|
296
|
+
path,
|
|
297
|
+
importer
|
|
298
|
+
}, ...[...foldedFiles.get(path) ?? []].map((file) => ({
|
|
299
|
+
path: file,
|
|
300
|
+
importer: path
|
|
301
|
+
}))];
|
|
302
|
+
for (let entry of entries) {
|
|
303
|
+
let file = !htmlDocument(entry.path) && existingFile(entry.path);
|
|
304
|
+
let refused = file && refusal(file, entry.importer);
|
|
305
|
+
if (refused && !refusals.get(refused.file)?.importer) refusals.set(refused.file, refused);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (refusals.size === 0) return;
|
|
309
|
+
let lines = [...refusals.values()].map(describe);
|
|
310
|
+
this.error(`[assets] Outside the browser boundary:\n ${lines.join("\n ")}`);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
//#endregion
|
|
25
315
|
//#region src/vite/output.ts
|
|
26
316
|
function captureOutput(bundle, config, role, assetFiles, entryFiles) {
|
|
27
317
|
let graph = {
|
|
@@ -31,8 +321,10 @@ function captureOutput(bundle, config, role, assetFiles, entryFiles) {
|
|
|
31
321
|
assets: Object.create(null)
|
|
32
322
|
};
|
|
33
323
|
let globalStylesheets = config.build.cssCodeSplit ? [] : Object.values(bundle).flatMap((output) => output.type === "asset" && output.originalFileNames.includes("style.css") ? [output.fileName] : []);
|
|
324
|
+
let linked = role === "server" ? linkedFiles(bundle, globalStylesheets) : void 0;
|
|
34
325
|
for (let output of Object.values(bundle)) {
|
|
35
326
|
if (output.type === "asset") {
|
|
327
|
+
if (linked && !linked.has(output.fileName)) continue;
|
|
36
328
|
let stylesheet = output.names.some(isCSSRequest);
|
|
37
329
|
for (let file of output.originalFileNames) {
|
|
38
330
|
if (stylesheet && !isCSSRequest(file)) continue;
|
|
@@ -54,6 +346,15 @@ function captureOutput(bundle, config, role, assetFiles, entryFiles) {
|
|
|
54
346
|
for (let [key, file] of entryFiles) if (bundle[file]?.type === "chunk") graph.entries[key] = file;
|
|
55
347
|
return graph;
|
|
56
348
|
}
|
|
349
|
+
function linkedFiles(bundle, globalStylesheets) {
|
|
350
|
+
let files = new Set(globalStylesheets);
|
|
351
|
+
for (let output of Object.values(bundle)) {
|
|
352
|
+
if (output.type !== "chunk" || !output.viteMetadata) continue;
|
|
353
|
+
for (let file of output.viteMetadata.importedCss) files.add(file);
|
|
354
|
+
for (let file of output.viteMetadata.importedAssets) files.add(file);
|
|
355
|
+
}
|
|
356
|
+
return files;
|
|
357
|
+
}
|
|
57
358
|
function captureImportMap(bundle, fileName) {
|
|
58
359
|
let output = bundle[fileName];
|
|
59
360
|
if (output?.type !== "asset") throw new Error(`[assets] The client build did not emit its configured import map "${fileName}".`);
|
|
@@ -324,7 +625,12 @@ function literalInputs(modules, id) {
|
|
|
324
625
|
}
|
|
325
626
|
//#endregion
|
|
326
627
|
//#region src/vite/state.ts
|
|
327
|
-
|
|
628
|
+
/**
|
|
629
|
+
* The manifest module a build writes at the root of each server environment's
|
|
630
|
+
* output directory. The server chunks that read the manifest import this file
|
|
631
|
+
* by a relative path, so a host that deploys a single module must bundle it in.
|
|
632
|
+
*/
|
|
633
|
+
const ASSETS_MANIFEST_FILE = "__pitlane_assets_manifest.js";
|
|
328
634
|
const MANIFEST_ID = "\0pitlane:assets-manifest";
|
|
329
635
|
const MANIFEST_EXTERNAL = "pitlane:assets-build-manifest";
|
|
330
636
|
const EMPTY_INPUT = "\0pitlane:assets-empty-input";
|
|
@@ -372,16 +678,26 @@ async function writeManifests(state) {
|
|
|
372
678
|
});
|
|
373
679
|
let output = state.outputs.get(name);
|
|
374
680
|
await mkdir(output.outDir, { recursive: true });
|
|
375
|
-
await writeFile(resolve(output.outDir,
|
|
681
|
+
await writeFile(resolve(output.outDir, ASSETS_MANIFEST_FILE), `export default JSON.parse(${JSON.stringify(JSON.stringify({
|
|
376
682
|
...manifest,
|
|
377
683
|
serverEnvironment: name
|
|
378
684
|
}))});\n`);
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
685
|
+
await publishLinkedFiles(output, twins.get(name), client.outDir);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Copies the stylesheets and assets a server graph links into the public client
|
|
690
|
+
* output, so every URL its manifest names is served. A stylesheet with a client
|
|
691
|
+
* twin already is.
|
|
692
|
+
*/
|
|
693
|
+
async function publishLinkedFiles(output, twin, publicDir) {
|
|
694
|
+
let linked = /* @__PURE__ */ new Set([...Object.values(output.graph.chunks).flatMap((chunk) => chunk.stylesheets), ...Object.values(output.graph.assets)]);
|
|
695
|
+
for (let file of linked) {
|
|
696
|
+
let asset = output.bundle[file];
|
|
697
|
+
if (asset?.type !== "asset" || twin.has(file)) continue;
|
|
698
|
+
let destination = resolve(publicDir, file);
|
|
699
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
700
|
+
await writeFile(destination, asset.source);
|
|
385
701
|
}
|
|
386
702
|
}
|
|
387
703
|
function importMapFileName(config) {
|
|
@@ -440,7 +756,7 @@ function assetBuild(state) {
|
|
|
440
756
|
},
|
|
441
757
|
renderChunk(code, chunk) {
|
|
442
758
|
if (!code.includes("pitlane:assets-build-manifest")) return;
|
|
443
|
-
let file = posix.relative(posix.dirname(chunk.fileName),
|
|
759
|
+
let file = posix.relative(posix.dirname(chunk.fileName), ASSETS_MANIFEST_FILE);
|
|
444
760
|
if (!file.startsWith(".")) file = `./${file}`;
|
|
445
761
|
return {
|
|
446
762
|
code: code.replaceAll(MANIFEST_EXTERNAL, file),
|
|
@@ -566,6 +882,26 @@ function assetStyles() {
|
|
|
566
882
|
};
|
|
567
883
|
}
|
|
568
884
|
//#endregion
|
|
885
|
+
//#region src/vite/transform-start.ts
|
|
886
|
+
let invalidations = 0;
|
|
887
|
+
let requestStart = new AsyncLocalStorage();
|
|
888
|
+
/** Counts a hard invalidation and returns its place on the timeline. */
|
|
889
|
+
function countInvalidation() {
|
|
890
|
+
return ++invalidations;
|
|
891
|
+
}
|
|
892
|
+
/** Runs `request`, which loads and transforms a module, as starting now. */
|
|
893
|
+
function startRequest(request) {
|
|
894
|
+
return requestStart.run(invalidations, request);
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* The place on the timeline where the calling transform's request started.
|
|
898
|
+
* A transform no request started, such as one a plugin runs through
|
|
899
|
+
* `pluginContainer.transform` itself, started where this is called.
|
|
900
|
+
*/
|
|
901
|
+
function requestStarted() {
|
|
902
|
+
return requestStart.getStore() ?? invalidations;
|
|
903
|
+
}
|
|
904
|
+
//#endregion
|
|
569
905
|
//#region src/vite/dev-graph.ts
|
|
570
906
|
/** CSS requests that import a string or URL rather than apply a stylesheet. */
|
|
571
907
|
const NON_STYLESHEET_QUERY = /[?&](?:inline|url|raw)\b/;
|
|
@@ -613,28 +949,55 @@ function followed(environment, id) {
|
|
|
613
949
|
return (noExternal === void 0 ? [] : Array.isArray(noExternal) ? noExternal : [noExternal]).some((pattern) => typeof pattern === "string" ? pattern === pkg : pattern.test(pkg));
|
|
614
950
|
}
|
|
615
951
|
/**
|
|
952
|
+
* A module is analyzed once. Its edge record was written by the transform
|
|
953
|
+
* hook, whether the runner or discovery ran that transform, and stays
|
|
954
|
+
* current until the module is invalidated, which drops the record. Keying
|
|
955
|
+
* this on `transformResult` instead would re-transform every module the
|
|
956
|
+
* runner has not executed on each rebuild.
|
|
957
|
+
*
|
|
958
|
+
* Client discovery goes through `transformRequest`, so it shares Vite's
|
|
959
|
+
* in-flight and cached transforms with the import analysis that warms a
|
|
960
|
+
* browser module's dependencies, and the browser later reads the cache.
|
|
616
961
|
* Server discovery must not populate `transformResult`: the module runner
|
|
617
|
-
*
|
|
618
|
-
*
|
|
962
|
+
* asks whether a module it already evaluated is still current by that
|
|
963
|
+
* field, so a transform cached between a file change and the runner's next
|
|
964
|
+
* import would keep its stale instance alive.
|
|
619
965
|
*/
|
|
620
966
|
async function analyze(environment, id, edges) {
|
|
621
|
-
if (
|
|
967
|
+
if (edges.has(id)) return;
|
|
622
968
|
let { root } = environment.config;
|
|
623
969
|
try {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
if (!fileModule(id)) throw new Error("No plugin loaded the module.");
|
|
629
|
-
code = await readFile(id.split("?")[0], "utf8");
|
|
970
|
+
let url = servedPath(root, id);
|
|
971
|
+
if (environment.config.consumer === "client") {
|
|
972
|
+
await environment.transformRequest(url.startsWith("/@id/") ? id : url);
|
|
973
|
+
if (edges.has(id)) return;
|
|
630
974
|
}
|
|
631
|
-
await environment
|
|
975
|
+
await startRequest(() => transformOutsideCache(environment, id, url));
|
|
632
976
|
} catch (error) {
|
|
633
977
|
let name = fileModule(id) ? sourceKey(root, id) : id;
|
|
634
978
|
throw new Error(`[assets] Could not analyze "${name}" for the "${environment.name}" development asset manifest: ${error.message}`, { cause: error });
|
|
635
979
|
}
|
|
636
980
|
}
|
|
637
981
|
/**
|
|
982
|
+
* Loads and transforms a module as `transformRequest` does, handing the load
|
|
983
|
+
* result's source map and module type to the transform, but without reading
|
|
984
|
+
* or populating Vite's transform cache.
|
|
985
|
+
*/
|
|
986
|
+
async function transformOutsideCache(environment, id, url) {
|
|
987
|
+
await environment.moduleGraph.ensureEntryFromUrl(url);
|
|
988
|
+
let loaded = await environment.pluginContainer.load(id);
|
|
989
|
+
let source = typeof loaded === "string" ? { code: loaded } : loaded;
|
|
990
|
+
let code = source?.code;
|
|
991
|
+
if (code == null) {
|
|
992
|
+
if (!fileModule(id)) throw new Error("No plugin loaded the module.");
|
|
993
|
+
code = await readFile(id.split("?")[0], "utf8");
|
|
994
|
+
}
|
|
995
|
+
await environment.pluginContainer.transform(code, id, {
|
|
996
|
+
inMap: source?.map,
|
|
997
|
+
moduleType: source?.moduleType
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
638
1001
|
* Every module reachable from `roots` through static and dynamic edges,
|
|
639
1002
|
* transforming each one the graph has not analyzed yet. Stylesheets are
|
|
640
1003
|
* leaves: their own `@import`s are inlined by Vite's CSS pipeline. Passing
|
|
@@ -784,12 +1147,48 @@ function assetDevelopment(state) {
|
|
|
784
1147
|
};
|
|
785
1148
|
let snapshots = /* @__PURE__ */ new Map();
|
|
786
1149
|
let building = /* @__PURE__ */ new Set();
|
|
787
|
-
/**
|
|
1150
|
+
/** Server environments invalidated while their snapshot was being built. */
|
|
1151
|
+
let stale = /* @__PURE__ */ new Set();
|
|
1152
|
+
/** Environment name → module id → where its last hard invalidation is on the transform timeline. */
|
|
1153
|
+
let invalidations = /* @__PURE__ */ new Map();
|
|
1154
|
+
/**
|
|
1155
|
+
* Invalidates a server environment's manifest and, through Vite, every
|
|
1156
|
+
* module importing it. A manifest being built is marked instead, and
|
|
1157
|
+
* `load` builds it again, since Vite would cache the code `load` returns.
|
|
1158
|
+
*/
|
|
788
1159
|
function invalidate(name, timestamp) {
|
|
1160
|
+
if (building.has(name)) return void stale.add(name);
|
|
789
1161
|
let environment = server?.environments[name];
|
|
790
1162
|
let manifest = environment?.moduleGraph.getModuleById(MANIFEST_ID);
|
|
791
1163
|
if (manifest) environment.moduleGraph.invalidateModule(manifest, /* @__PURE__ */ new Set(), timestamp, true);
|
|
792
1164
|
}
|
|
1165
|
+
/** The modules of `environment` a server environment's snapshot was computed from. */
|
|
1166
|
+
function tracked(environment, serverName) {
|
|
1167
|
+
let snapshot = snapshots.get(serverName);
|
|
1168
|
+
if (environment === serverName) return snapshot?.server;
|
|
1169
|
+
if (environment === "client") return snapshot?.client;
|
|
1170
|
+
}
|
|
1171
|
+
/** Invalidates every snapshot computed, or being computed, from module `id` of `environment`. */
|
|
1172
|
+
function invalidateSnapshotsWith(environment, id, timestamp) {
|
|
1173
|
+
for (let serverName of snapshots.keys()) if (tracked(environment, serverName)?.has(id)) invalidate(serverName, timestamp);
|
|
1174
|
+
for (let serverName of building) if (environment === serverName || environment === "client") stale.add(serverName);
|
|
1175
|
+
}
|
|
1176
|
+
function sameEdges(previous, next) {
|
|
1177
|
+
return previous.static.length === next.static.length && previous.dynamic.length === next.dynamic.length && previous.static.every((edge, index) => edge === next.static[index]) && previous.dynamic.every((edge, index) => edge === next.dynamic[index]);
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Records what a transform of module `id` found: its edges and, in a
|
|
1181
|
+
* server environment, its resolver usage.
|
|
1182
|
+
*/
|
|
1183
|
+
function recordTransform(environment, id, edges, usage) {
|
|
1184
|
+
let edgeRecords = environmentEdges(graph, environment);
|
|
1185
|
+
let previous = edgeRecords.get(id);
|
|
1186
|
+
edgeRecords.set(id, edges);
|
|
1187
|
+
if (previous && !sameEdges(previous, edges)) invalidateSnapshotsWith(environment, id);
|
|
1188
|
+
let usageRecords = graph.resolverUsage.get(environment);
|
|
1189
|
+
if (usage) usageRecords?.set(id, usage);
|
|
1190
|
+
else usageRecords?.delete(id);
|
|
1191
|
+
}
|
|
793
1192
|
return {
|
|
794
1193
|
name: "pitlane-assets-dev",
|
|
795
1194
|
apply: "serve",
|
|
@@ -799,10 +1198,26 @@ function assetDevelopment(state) {
|
|
|
799
1198
|
handler(devServer) {
|
|
800
1199
|
server = devServer;
|
|
801
1200
|
for (let environment of Object.values(devServer.environments)) {
|
|
802
|
-
let { name } = environment;
|
|
1201
|
+
let { name, moduleGraph } = environment;
|
|
1202
|
+
let edges = environmentEdges(graph, name);
|
|
1203
|
+
let invalidated = /* @__PURE__ */ new Map();
|
|
1204
|
+
invalidations.set(name, invalidated);
|
|
1205
|
+
let invalidateModule = moduleGraph.invalidateModule;
|
|
1206
|
+
moduleGraph.invalidateModule = (mod, seen, timestamp, isHmr, softInvalidate) => {
|
|
1207
|
+
if (!softInvalidate && mod.id && mod.id !== "\0pitlane:assets-manifest" && !isCSSRequest(mod.id)) {
|
|
1208
|
+
edges.delete(mod.id);
|
|
1209
|
+
if (!isHmr) invalidated.set(mod.id, countInvalidation());
|
|
1210
|
+
invalidateSnapshotsWith(name, mod.id, timestamp);
|
|
1211
|
+
}
|
|
1212
|
+
invalidateModule.call(moduleGraph, mod, seen, timestamp, isHmr, softInvalidate);
|
|
1213
|
+
};
|
|
1214
|
+
let { transformRequest, warmupRequest } = environment;
|
|
1215
|
+
environment.transformRequest = (url) => startRequest(() => transformRequest.call(environment, url));
|
|
1216
|
+
environment.warmupRequest = (url) => startRequest(() => warmupRequest.call(environment, url));
|
|
803
1217
|
if (!state.serverEnvironments.includes(name)) continue;
|
|
804
1218
|
let roots = /* @__PURE__ */ new Set();
|
|
805
1219
|
graph.roots.set(name, roots);
|
|
1220
|
+
graph.resolverUsage.set(name, /* @__PURE__ */ new Map());
|
|
806
1221
|
let fetchModule = environment.fetchModule;
|
|
807
1222
|
environment.fetchModule = async (id, importer, options) => {
|
|
808
1223
|
let fetched = await fetchModule.call(environment, id, importer, options);
|
|
@@ -830,32 +1245,45 @@ function assetDevelopment(state) {
|
|
|
830
1245
|
if (!state.serverEnvironments.includes(name)) throw unservedEnvironmentError(name, state.serverEnvironments);
|
|
831
1246
|
building.add(name);
|
|
832
1247
|
try {
|
|
833
|
-
let snapshot
|
|
1248
|
+
let snapshot;
|
|
1249
|
+
let passes = 0;
|
|
1250
|
+
do {
|
|
1251
|
+
stale.delete(name);
|
|
1252
|
+
snapshot = await createDevSnapshot(server, state, graph, name);
|
|
1253
|
+
} while (stale.has(name) && ++passes < 3);
|
|
834
1254
|
snapshots.set(name, snapshot);
|
|
1255
|
+
if (stale.has(name)) {
|
|
1256
|
+
let { moduleGraph } = server.environments[name];
|
|
1257
|
+
moduleGraph.invalidateModule(await moduleGraph.ensureEntryFromUrl(MANIFEST_ID));
|
|
1258
|
+
}
|
|
835
1259
|
return `export default JSON.parse(${JSON.stringify(JSON.stringify(snapshot.manifest))});\n`;
|
|
836
1260
|
} finally {
|
|
837
1261
|
building.delete(name);
|
|
1262
|
+
stale.delete(name);
|
|
838
1263
|
}
|
|
839
1264
|
},
|
|
840
1265
|
transform: { async handler(code, id) {
|
|
841
1266
|
if (id === "\0pitlane:assets-manifest" || isCSSRequest(id)) return;
|
|
842
1267
|
let name = this.environment.name;
|
|
843
|
-
let
|
|
1268
|
+
let invalidated = invalidations.get(name);
|
|
1269
|
+
let started = requestStarted();
|
|
1270
|
+
let resolve = (specifier) => this.resolve(specifier, id);
|
|
844
1271
|
let resolveAll = async (list) => {
|
|
845
|
-
let resolved = await Promise.all(list.map(
|
|
1272
|
+
let resolved = await Promise.all(list.map(resolve));
|
|
846
1273
|
return [...new Set(resolved.flatMap((result) => result && !result.external ? [result.id] : []))];
|
|
847
1274
|
};
|
|
848
|
-
let
|
|
849
|
-
|
|
1275
|
+
let specifiers = importSpecifiers(code);
|
|
1276
|
+
let usage = state.serverEnvironments.includes(name) ? scanResolverUsage(code, id) : void 0;
|
|
1277
|
+
let [staticEdges, dynamicEdges, linkedUsage] = await Promise.all([
|
|
1278
|
+
resolveAll(specifiers.static),
|
|
1279
|
+
resolveAll(specifiers.dynamic),
|
|
1280
|
+
usage && linkResolverUsage(usage, resolve)
|
|
1281
|
+
]);
|
|
1282
|
+
if ((invalidated?.get(id) ?? 0) > started) return;
|
|
1283
|
+
recordTransform(name, id, {
|
|
850
1284
|
static: staticEdges,
|
|
851
1285
|
dynamic: dynamicEdges
|
|
852
|
-
});
|
|
853
|
-
if (!state.serverEnvironments.includes(name)) return;
|
|
854
|
-
let modules = graph.resolverUsage.get(name);
|
|
855
|
-
if (!modules) graph.resolverUsage.set(name, modules = /* @__PURE__ */ new Map());
|
|
856
|
-
let usage = scanResolverUsage(code, id);
|
|
857
|
-
if (!usage) return void modules.delete(id);
|
|
858
|
-
modules.set(id, await linkResolverUsage(usage, (specifier) => this.resolve(specifier, id)));
|
|
1286
|
+
}, linkedUsage);
|
|
859
1287
|
} },
|
|
860
1288
|
hotUpdate: {
|
|
861
1289
|
order: "post",
|
|
@@ -868,9 +1296,9 @@ function assetDevelopment(state) {
|
|
|
868
1296
|
}
|
|
869
1297
|
let name = this.environment.name;
|
|
870
1298
|
let ids = modules.flatMap((module) => module.id ? [module.id] : []);
|
|
871
|
-
for (let
|
|
872
|
-
let
|
|
873
|
-
if (
|
|
1299
|
+
for (let serverName of snapshots.keys()) {
|
|
1300
|
+
let set = tracked(name, serverName);
|
|
1301
|
+
if (set && (set.has(path) || ids.some((id) => set.has(id)))) invalidate(serverName, timestamp);
|
|
874
1302
|
}
|
|
875
1303
|
}
|
|
876
1304
|
}
|
|
@@ -878,7 +1306,17 @@ function assetDevelopment(state) {
|
|
|
878
1306
|
}
|
|
879
1307
|
//#endregion
|
|
880
1308
|
//#region src/vite-plugin.ts
|
|
1309
|
+
/**
|
|
1310
|
+
* The Vite plugins that give `@pitlane/assets` its manifest: they serve
|
|
1311
|
+
* browser entries during `vite dev`, emit them in a build, and replace
|
|
1312
|
+
* `@pitlane/assets/manifest` with the manifest of the current dev server or
|
|
1313
|
+
* build. Setting `allowFiles` also enforces the browser boundary.
|
|
1314
|
+
*
|
|
1315
|
+
* @throws When `allowPackages` or `denyFiles` is set without `allowFiles`.
|
|
1316
|
+
*/
|
|
881
1317
|
function assets(options = {}) {
|
|
1318
|
+
let { allowFiles, allowPackages, denyFiles } = options;
|
|
1319
|
+
if (!allowFiles && (allowPackages || denyFiles)) throw new Error("[assets] allowPackages and denyFiles refine allowFiles. Set allowFiles to turn on the browser boundary.");
|
|
882
1320
|
let state = {
|
|
883
1321
|
serverEnvironments: options.serverEnvironments ?? ["ssr"],
|
|
884
1322
|
inputs: /* @__PURE__ */ new Map(),
|
|
@@ -961,8 +1399,13 @@ function assets(options = {}) {
|
|
|
961
1399
|
},
|
|
962
1400
|
assetBuild(state),
|
|
963
1401
|
assetDevelopment(state),
|
|
964
|
-
assetStyles()
|
|
1402
|
+
assetStyles(),
|
|
1403
|
+
allowFiles && browserBoundary({
|
|
1404
|
+
allowFiles,
|
|
1405
|
+
allowPackages,
|
|
1406
|
+
denyFiles
|
|
1407
|
+
}, state.serverEnvironments)
|
|
965
1408
|
];
|
|
966
1409
|
}
|
|
967
1410
|
//#endregion
|
|
968
|
-
export { assets };
|
|
1411
|
+
export { ASSETS_MANIFEST_FILE, assets };
|
package/package.json
CHANGED
|
@@ -1,76 +1,74 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
2
|
+
"name": "@pitlane/assets",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Framework-neutral asset resolution for bundled applications.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"assets",
|
|
7
|
+
"import-map",
|
|
8
|
+
"manifest",
|
|
9
|
+
"modulepreload",
|
|
10
|
+
"pitlane",
|
|
11
|
+
"vite",
|
|
12
|
+
"vite-plugin"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://pitlane.tools/package/assets/",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/pitlane-tools/pitlane/issues"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Mark Malstrom <mark@malstrom.me>",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/pitlane-tools/pitlane.git",
|
|
23
|
+
"directory": "packages/assets"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"CHANGELOG.md",
|
|
28
|
+
"LICENSE.upstream"
|
|
29
|
+
],
|
|
30
|
+
"type": "module",
|
|
31
|
+
"types": "./dist/index.d.mts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.mts",
|
|
35
|
+
"import": "./dist/index.mjs"
|
|
17
36
|
},
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"type": "git",
|
|
22
|
-
"url": "git+https://github.com/pitlane-tools/pitlane.git",
|
|
23
|
-
"directory": "packages/assets"
|
|
37
|
+
"./manifest": {
|
|
38
|
+
"types": "./dist/manifest.d.mts",
|
|
39
|
+
"import": "./dist/manifest.mjs"
|
|
24
40
|
},
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"LICENSE.upstream"
|
|
29
|
-
],
|
|
30
|
-
"type": "module",
|
|
31
|
-
"types": "./dist/index.d.mts",
|
|
32
|
-
"exports": {
|
|
33
|
-
".": {
|
|
34
|
-
"types": "./dist/index.d.mts",
|
|
35
|
-
"import": "./dist/index.mjs"
|
|
36
|
-
},
|
|
37
|
-
"./manifest": {
|
|
38
|
-
"types": "./dist/manifest.d.mts",
|
|
39
|
-
"import": "./dist/manifest.mjs"
|
|
40
|
-
},
|
|
41
|
-
"./build": {
|
|
42
|
-
"types": "./dist/build.d.mts",
|
|
43
|
-
"import": "./dist/build.mjs"
|
|
44
|
-
},
|
|
45
|
-
"./vite-plugin": {
|
|
46
|
-
"types": "./dist/vite-plugin.d.mts",
|
|
47
|
-
"import": "./dist/vite-plugin.mjs"
|
|
48
|
-
}
|
|
41
|
+
"./build": {
|
|
42
|
+
"types": "./dist/build.d.mts",
|
|
43
|
+
"import": "./dist/build.mjs"
|
|
49
44
|
},
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"engines": {
|
|
74
|
-
"node": "^20.19.0 || >=22.12.0"
|
|
45
|
+
"./vite-plugin": {
|
|
46
|
+
"types": "./dist/vite-plugin.d.mts",
|
|
47
|
+
"import": "./dist/vite-plugin.mjs"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"magic-string": "^0.30.21"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^25.5.0",
|
|
55
|
+
"fast-check": "4.10.2",
|
|
56
|
+
"playwright": "1.61.1",
|
|
57
|
+
"remix": "3.0.0",
|
|
58
|
+
"typescript": "^7.0.2",
|
|
59
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@^1.0.0",
|
|
60
|
+
"vite-plus": "^1.0.0"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"vite": ">=8.1.0"
|
|
64
|
+
},
|
|
65
|
+
"peerDependenciesMeta": {
|
|
66
|
+
"vite": {
|
|
67
|
+
"optional": true
|
|
75
68
|
}
|
|
76
|
-
}
|
|
69
|
+
},
|
|
70
|
+
"engines": {
|
|
71
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {}
|
|
74
|
+
}
|