@pracht/vite-plugin 0.7.0 → 0.7.1

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/dist/index.d.mts CHANGED
@@ -28,7 +28,8 @@ interface EnvLeakReference {
28
28
  }
29
29
  /**
30
30
  * Scans JavaScript source for references to environment variables that are
31
- * neither public-prefixed, Vite built-ins, nor explicitly allowed.
31
+ * neither public-prefixed, Vite built-ins, nor explicitly allowed, plus reads
32
+ * that pull in the whole `import.meta.env` object.
32
33
  */
33
34
  declare function scanCodeForEnvLeaks(code: string, allow?: ReadonlySet<string>): EnvLeakReference[];
34
35
  interface EnvLeakProblem extends EnvLeakReference {
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
3
3
  import { preactSsrPrecompile } from "@pracht/preact-ssr-precompile";
4
4
  import preact from "@preact/preset-vite";
5
5
  import { dirname, extname, join, resolve } from "node:path";
6
- import { parseAst } from "vite";
6
+ import { loadEnv, parseAst } from "vite";
7
7
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
8
8
  import { CAPABILITY_SETTLED_EVENT, CAPABILITY_TRANSPORT_HEADER, CONFIRMATION_HEADER, capabilityHttpPath, isValidCapabilityHttpPath } from "@pracht/capabilities";
9
9
  import { evaluateLiteral, extractCapabilityRegistrations, extractDefineCapabilityArgs, scanTopLevelProperties } from "@pracht/capabilities/static";
@@ -909,30 +909,51 @@ const VITE_BUILTIN_ENV_VARS = new Set([
909
909
  const PUBLIC_ENV_PREFIX = "PRACHT_PUBLIC_";
910
910
  /** Server-only core entry that must never resolve into client bundles. */
911
911
  const SERVER_ENV_MODULE_ID = "@pracht/core/env/server";
912
- const ENV_REFERENCE_RE = /\b(process\.env|import\.meta\.env)(?:\.([A-Za-z_$][A-Za-z0-9_$]*)|\[\s*(["'])([A-Za-z_$][A-Za-z0-9_$]*)\3\s*\])/g;
912
+ const ENV_REFERENCE_RE = /\b(process\.env|import\.meta\.env)(?:\??\.([A-Za-z_$][A-Za-z0-9_$]*)|(?:\?\.)?\[\s*(["'])([A-Za-z_$][A-Za-z0-9_$]*)\3\s*\])/g;
913
+ const WHOLE_ENV_READ_RE = /\bimport\.meta\.env\b(?!\s*\??\.\s*[A-Za-z_$])/g;
913
914
  /**
914
915
  * Scans JavaScript source for references to environment variables that are
915
- * neither public-prefixed, Vite built-ins, nor explicitly allowed.
916
+ * neither public-prefixed, Vite built-ins, nor explicitly allowed, plus reads
917
+ * that pull in the whole `import.meta.env` object.
916
918
  */
917
919
  function scanCodeForEnvLeaks(code, allow = /* @__PURE__ */ new Set()) {
918
- const findings = [];
919
- const seen = /* @__PURE__ */ new Set();
920
920
  const codePositions = getCodePositionMask(code);
921
+ const matches = [];
921
922
  for (const match of code.matchAll(ENV_REFERENCE_RE)) {
922
- if (!codePositions[match.index ?? -1]) continue;
923
+ const index = match.index ?? -1;
924
+ if (!codePositions[index]) continue;
923
925
  const accessor = match[1];
924
926
  const name = match[2] ?? match[4];
925
927
  if (!name) continue;
926
928
  if (name.startsWith("PRACHT_PUBLIC_")) continue;
927
929
  if (VITE_BUILTIN_ENV_VARS.has(name)) continue;
928
930
  if (allow.has(name)) continue;
929
- const key = `${accessor}.${name}`;
931
+ matches.push({
932
+ index,
933
+ reference: {
934
+ accessor,
935
+ name
936
+ }
937
+ });
938
+ }
939
+ if (!allow.has("*")) for (const match of code.matchAll(WHOLE_ENV_READ_RE)) {
940
+ const index = match.index ?? -1;
941
+ if (!codePositions[index]) continue;
942
+ matches.push({
943
+ index,
944
+ reference: {
945
+ accessor: "import.meta.env",
946
+ name: "*"
947
+ }
948
+ });
949
+ }
950
+ const findings = [];
951
+ const seen = /* @__PURE__ */ new Set();
952
+ for (const { reference } of matches.sort((a, b) => a.index - b.index)) {
953
+ const key = `${reference.accessor}.${reference.name}`;
930
954
  if (seen.has(key)) continue;
931
955
  seen.add(key);
932
- findings.push({
933
- accessor,
934
- name
935
- });
956
+ findings.push(reference);
936
957
  }
937
958
  return findings;
938
959
  }
@@ -1103,12 +1124,20 @@ function isIdentifierChar(char) {
1103
1124
  return !!char && /[A-Za-z0-9_$]/.test(char);
1104
1125
  }
1105
1126
  function formatEnvLeakError(problems) {
1127
+ const lines = problems.map((problem) => {
1128
+ const source = problem.sources.length > 0 ? ` (likely from ${problem.sources.map((file) => JSON.stringify(file)).join(", ")})` : "";
1129
+ return ` - ${problem.name === "*" ? "import.meta.env read as a whole object" : `${problem.accessor}.${problem.name}`} in chunk "${problem.chunk}"${source}`;
1130
+ });
1131
+ const wholeEnvGuidance = problems.some((problem) => problem.name === "*") ? [
1132
+ "",
1133
+ "A whole-object `import.meta.env` read (bare reference, destructuring, spread, or bracket access)",
1134
+ "is replaced at build time by an object literal containing every exposed variable — including the",
1135
+ "`VITE_` values Pracht does not treat as public. Read one key at a time (`import.meta.env.KEY`)."
1136
+ ] : [];
1106
1137
  return [
1107
1138
  "[pracht] Environment variable leak detected in the client bundle:",
1108
- ...problems.map((problem) => {
1109
- const source = problem.sources.length > 0 ? ` (likely from ${problem.sources.map((file) => JSON.stringify(file)).join(", ")})` : "";
1110
- return ` - ${problem.accessor}.${problem.name} in chunk "${problem.chunk}"${source}`;
1111
- }),
1139
+ ...lines,
1140
+ ...wholeEnvGuidance,
1112
1141
  "",
1113
1142
  `Only PRACHT_PUBLIC_-prefixed variables may be referenced in client code (prefer publicEnv from "@pracht/core" for typed public values).`,
1114
1143
  `Move server-only reads into loaders/API routes and access them via serverEnv from "@pracht/core/env/server",`,
@@ -1193,6 +1222,7 @@ function createEnvSafetyPlugin(envSafety) {
1193
1222
  //#region src/plugin-assets.ts
1194
1223
  const PRACHT_CLIENT_MODULE_ID = "virtual:pracht/client";
1195
1224
  const PRACHT_SERVER_MODULE_ID = "virtual:pracht/server";
1225
+ const PRACHT_DEV_MODULE_ID = "virtual:pracht/dev-metadata";
1196
1226
  const PRACHT_ISLANDS_CLIENT_MODULE_ID = "virtual:pracht/islands-client";
1197
1227
  const PRACHT_CAPABILITIES_MODULE_ID = "virtual:pracht/capabilities";
1198
1228
  const PRACHT_WEBMCP_MODULE_ID = "virtual:pracht/webmcp";
@@ -1258,6 +1288,9 @@ function isClientModule(id) {
1258
1288
  function isServerModule(id) {
1259
1289
  return id === "virtual:pracht/server" || id.endsWith("virtual:pracht/server");
1260
1290
  }
1291
+ function isDevModule(id) {
1292
+ return id === "virtual:pracht/dev-metadata" || id.endsWith("virtual:pracht/dev-metadata");
1293
+ }
1261
1294
  function isIslandsClientModule(id) {
1262
1295
  return id === "virtual:pracht/islands-client" || id === "/@pracht/islands.js" || id.endsWith("virtual:pracht/islands-client");
1263
1296
  }
@@ -1891,6 +1924,23 @@ function createPrachtServerModuleSource(options = {}, buildOptions = {}) {
1891
1924
  return source.join("\n");
1892
1925
  }
1893
1926
  /**
1927
+ * Adapter-neutral app metadata used by development tooling. Keeping this
1928
+ * separate from the server entry avoids evaluating worker-only imports (for
1929
+ * example `cloudflare:workers`) in Vite's Node SSR environment.
1930
+ */
1931
+ function createPrachtDevModuleSource(options = {}, buildOptions = {}) {
1932
+ const resolved = resolveOptions(options);
1933
+ return [
1934
+ "import { resolveApp } from \"@pracht/core/server\";",
1935
+ resolved.pagesDir ? generatePagesAppInlineSource(resolved, buildOptions.root) : `import { app } from ${JSON.stringify(resolved.appFile)};`,
1936
+ "",
1937
+ createPrachtRegistryModuleSource(resolved),
1938
+ "",
1939
+ "export const resolvedApp = resolveApp(app);",
1940
+ ""
1941
+ ].join("\n");
1942
+ }
1943
+ /**
1894
1944
  * Fill llms.txt title/description from the app's package.json when the user
1895
1945
  * did not set them explicitly. Returns null when the feature is disabled so
1896
1946
  * the server module codegen stays byte-for-byte unchanged.
@@ -1995,6 +2045,7 @@ function generatePagesAppInlineSource(options, root = process.cwd()) {
1995
2045
  //#region src/plugin-dev-ssr.ts
1996
2046
  const BODYLESS_METHODS = new Set(["GET", "HEAD"]);
1997
2047
  const DEFAULT_MAX_BODY_SIZE = 1024 * 1024;
2048
+ const CSS_MODULE_URL_RE = /\.(?:css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
1998
2049
  const DEVTOOLS_JSON_PATH = "/_pracht.json";
1999
2050
  const LLMS_TXT_PATH = "/llms.txt";
2000
2051
  function createDevSSRMiddleware(server, options = {}) {
@@ -2080,6 +2131,154 @@ function createDevSSRMiddleware(server, options = {}) {
2080
2131
  };
2081
2132
  }
2082
2133
  /**
2134
+ * Build the development equivalent of the production CSS manifest for the
2135
+ * current route. Vite turns CSS imports into client-side style injection by
2136
+ * default; resolving the same imports through the active server environment
2137
+ * graphs lets pracht put real stylesheet links in the initial document and
2138
+ * avoid a first-paint FOUC.
2139
+ */
2140
+ async function createDevCssManifest(server, options) {
2141
+ const route = options.matchAppRoute(options.app, options.pathname)?.route ?? options.app.notFound;
2142
+ if (!route) return {};
2143
+ const manifest = {};
2144
+ const modules = [...route.shellFile ? [{
2145
+ file: route.shellFile,
2146
+ registry: options.registry.shellModules
2147
+ }] : [], {
2148
+ file: route.file,
2149
+ registry: options.registry.routeModules
2150
+ }];
2151
+ const results = await Promise.all(modules.map(async ({ file, registry }) => {
2152
+ if (!registry) return {
2153
+ file,
2154
+ urls: []
2155
+ };
2156
+ const moduleKey = findRegistryModuleKey(registry, file);
2157
+ if (!moduleKey) return {
2158
+ file,
2159
+ urls: []
2160
+ };
2161
+ const entries = await Promise.all(Object.values(server.environments).map((environment) => environment.moduleGraph.getModuleByUrl(moduleKey)));
2162
+ return {
2163
+ file,
2164
+ urls: [...new Set(entries.flatMap((entry) => collectDevCssUrls(entry)))]
2165
+ };
2166
+ }));
2167
+ for (const { file, urls } of results) if (urls.length > 0) manifest[file] = urls;
2168
+ return manifest;
2169
+ }
2170
+ function findRegistryModuleKey(modules, file) {
2171
+ if (!modules) return void 0;
2172
+ if (file in modules) return file;
2173
+ const suffix = `/${file.split("?")[0].replace(/\\/g, "/").replace(/^\.?\//, "")}`;
2174
+ return Object.keys(modules).find((key) => key.split("?")[0].replace(/\\/g, "/").endsWith(suffix));
2175
+ }
2176
+ function collectDevCssUrls(entry) {
2177
+ if (!entry) return [];
2178
+ const urls = /* @__PURE__ */ new Set();
2179
+ const visited = /* @__PURE__ */ new Set();
2180
+ const pending = [entry];
2181
+ while (pending.length > 0) {
2182
+ const module = pending.pop();
2183
+ if (visited.has(module)) continue;
2184
+ visited.add(module);
2185
+ if ((module.type === "css" || CSS_MODULE_URL_RE.test(module.url)) && !/[?&](?:inline|raw|url)(?:[=&]|$)/.test(module.url)) urls.add(module.url);
2186
+ pending.push(...[...module.importedModules].reverse());
2187
+ }
2188
+ return [...urls];
2189
+ }
2190
+ function injectDevCssLinks(html, manifest) {
2191
+ if (!html.includes("</head>")) return html;
2192
+ const tags = [...new Set(Object.values(manifest).flat())].map((url) => escapeHtmlAttribute(url)).filter((escapedUrl) => !html.includes(`href="${escapedUrl}"`)).map((escapedUrl) => `<link rel="stylesheet" href="${escapedUrl}">`);
2193
+ if (tags.length === 0) return html;
2194
+ return html.replace("</head>", ` ${tags.join("\n ")}\n </head>`);
2195
+ }
2196
+ async function injectDevCssForPath(server, path, html) {
2197
+ return injectDevCssLinks(html, await createDevCssManifest(server, await resolveDevCssContextForPath(server, path)));
2198
+ }
2199
+ async function resolveDevCssContextForPath(server, path) {
2200
+ const [framework, serverMod] = await Promise.all([server.ssrLoadModule("@pracht/core/server"), server.ssrLoadModule(PRACHT_DEV_MODULE_ID)]);
2201
+ const pathname = new URL(path, "http://localhost").pathname;
2202
+ return {
2203
+ app: serverMod.resolvedApp,
2204
+ matchAppRoute: framework.matchAppRoute,
2205
+ pathname,
2206
+ registry: serverMod.registry
2207
+ };
2208
+ }
2209
+ /**
2210
+ * Adapter-owned dev servers (for example Cloudflare's worker runtime) bypass
2211
+ * Vite's HTML transform hooks. Install this before the adapter middleware so
2212
+ * document responses still receive the same parser-blocking stylesheet links.
2213
+ */
2214
+ function createDevCssInjectionMiddleware(server) {
2215
+ let warned = false;
2216
+ return (req, res, next) => {
2217
+ const method = (req.method ?? "GET").toUpperCase();
2218
+ const accept = readRequestHeader(req.headers.accept).toLowerCase();
2219
+ if (method !== "GET" || !accept.includes("text/html")) {
2220
+ next();
2221
+ return;
2222
+ }
2223
+ const contextPromise = resolveDevCssContextForPath(server, req.url ?? "/").catch((error) => {
2224
+ if (!warned) {
2225
+ warned = true;
2226
+ server.config.logger.warn(`[pracht] Could not discover development stylesheets: ${error instanceof Error ? error.message : String(error)}`);
2227
+ }
2228
+ return null;
2229
+ });
2230
+ const chunks = [];
2231
+ const originalEnd = res.end.bind(res);
2232
+ const originalWriteHead = res.writeHead.bind(res);
2233
+ res.writeHead = ((statusCode, ...args) => {
2234
+ res.removeHeader("content-length");
2235
+ return Reflect.apply(originalWriteHead, res, [statusCode, ...args.map(stripContentLengthHeader)]);
2236
+ });
2237
+ res.write = ((chunk, encodingOrCallback, callback) => {
2238
+ chunks.push(toBuffer(chunk, encodingOrCallback));
2239
+ (typeof encodingOrCallback === "function" ? encodingOrCallback : typeof callback === "function" ? callback : void 0)?.();
2240
+ return true;
2241
+ });
2242
+ res.end = ((chunk, encodingOrCallback, callback) => {
2243
+ if (chunk != null) chunks.push(toBuffer(chunk, encodingOrCallback));
2244
+ const done = typeof encodingOrCallback === "function" ? encodingOrCallback : typeof callback === "function" ? callback : void 0;
2245
+ (async () => {
2246
+ const body = Buffer.concat(chunks);
2247
+ if (!String(res.getHeader("content-type") ?? "").includes("text/html")) {
2248
+ originalEnd(body, done);
2249
+ return;
2250
+ }
2251
+ try {
2252
+ const context = await contextPromise;
2253
+ const manifest = context ? await createDevCssManifest(server, context) : null;
2254
+ originalEnd(manifest ? injectDevCssLinks(body.toString("utf-8"), manifest) : body.toString("utf-8"), done);
2255
+ } catch {
2256
+ originalEnd(body, done);
2257
+ }
2258
+ })();
2259
+ return res;
2260
+ });
2261
+ next();
2262
+ };
2263
+ }
2264
+ function toBuffer(chunk, encoding) {
2265
+ if (Buffer.isBuffer(chunk)) return chunk;
2266
+ if (chunk instanceof Uint8Array) return Buffer.from(chunk);
2267
+ return Buffer.from(String(chunk), typeof encoding === "string" ? encoding : void 0);
2268
+ }
2269
+ function stripContentLengthHeader(value) {
2270
+ if (Array.isArray(value)) {
2271
+ const headers = [];
2272
+ for (let index = 0; index < value.length; index += 2) if (String(value[index]).toLowerCase() !== "content-length") headers.push(value[index], value[index + 1]);
2273
+ return headers;
2274
+ }
2275
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).filter(([name]) => name.toLowerCase() !== "content-length"));
2276
+ return value;
2277
+ }
2278
+ function escapeHtmlAttribute(value) {
2279
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2280
+ }
2281
+ /**
2083
2282
  * Serve the dev-only `/_pracht` devtools page (or `/_pracht.json`) built from
2084
2283
  * the same resolved app graph that `pracht inspect` reports.
2085
2284
  */
@@ -2295,9 +2494,12 @@ function pracht(options = {}) {
2295
2494
  const isSSRBuild = env.isSsrBuild;
2296
2495
  const configRoot = _config.root ?? process.cwd();
2297
2496
  const wantsIslandsEntry = env.command === "build" && !isSSRBuild && existsSync(resolveConfigPath(configRoot, resolved.islandsDir));
2497
+ const envDir = _config.envDir ? resolve(configRoot, _config.envDir) : configRoot;
2498
+ const publicEnvDefine = JSON.stringify(loadEnv(env.mode, envDir, PUBLIC_ENV_PREFIX));
2298
2499
  return {
2299
2500
  appType: "custom",
2300
2501
  envPrefix: ["VITE_", PUBLIC_ENV_PREFIX],
2502
+ define: { __PRACHT_PUBLIC_ENV__: publicEnvDefine },
2301
2503
  ...isSSRBuild ? {} : { build: { rollupOptions: {
2302
2504
  ...wantsIslandsEntry ? { input: [PRACHT_ISLANDS_CLIENT_MODULE_ID] } : {},
2303
2505
  output: { manualChunks(id) {
@@ -2321,6 +2523,7 @@ function pracht(options = {}) {
2321
2523
  resolveId(id, importer, resolveIdOptions) {
2322
2524
  if (isIslandsClientModule(id)) return PRACHT_ISLANDS_CLIENT_MODULE_ID;
2323
2525
  if (isClientModule(id)) return PRACHT_CLIENT_MODULE_ID;
2526
+ if (isDevModule(id)) return PRACHT_DEV_MODULE_ID;
2324
2527
  if (isServerModule(id)) return PRACHT_SERVER_MODULE_ID;
2325
2528
  if (isCapabilitiesModule(id)) return PRACHT_CAPABILITIES_MODULE_ID;
2326
2529
  if (isWebmcpModule(id)) return PRACHT_WEBMCP_MODULE_ID;
@@ -2330,6 +2533,7 @@ function pracht(options = {}) {
2330
2533
  load(id) {
2331
2534
  if (isIslandsClientModule(id)) return createPrachtIslandsClientModuleSource(resolved, { root });
2332
2535
  if (isClientModule(id)) return createPrachtClientModuleSource(resolved, { root });
2536
+ if (isDevModule(id)) return createPrachtDevModuleSource(resolved, { root });
2333
2537
  if (isServerModule(id)) return createPrachtServerModuleSource(resolved, {
2334
2538
  root,
2335
2539
  isBuild
@@ -2350,7 +2554,10 @@ function pracht(options = {}) {
2350
2554
  },
2351
2555
  configureServer(server) {
2352
2556
  if (isPagesMode) watchPagesDirectory(server, resolved, root);
2353
- if (resolved.adapter.ownsDevServer) return;
2557
+ if (resolved.adapter.ownsDevServer) {
2558
+ server.middlewares.use(createDevCssInjectionMiddleware(server));
2559
+ return;
2560
+ }
2354
2561
  return () => {
2355
2562
  server.middlewares.use(createDevSSRMiddleware(server, {
2356
2563
  llmsTxt: !!resolved.llmsTxt,
@@ -2358,6 +2565,14 @@ function pracht(options = {}) {
2358
2565
  }));
2359
2566
  };
2360
2567
  },
2568
+ async transformIndexHtml(html, context) {
2569
+ if (isBuild || !context.server || !html.includes("</head>")) return html;
2570
+ try {
2571
+ return await injectDevCssForPath(context.server, context.path, html);
2572
+ } catch {
2573
+ return html;
2574
+ }
2575
+ },
2361
2576
  handleHotUpdate({ file, server }) {
2362
2577
  const serverRoot = toPosixPath(server.config.root);
2363
2578
  const normalizedFile = toPosixPath(file);
@@ -2382,6 +2597,8 @@ function pracht(options = {}) {
2382
2597
  ].some((dir) => relative.startsWith(dir))) {
2383
2598
  const serverMod = server.moduleGraph.getModuleById(PRACHT_SERVER_MODULE_ID);
2384
2599
  if (serverMod) server.moduleGraph.invalidateModule(serverMod);
2600
+ const devMod = server.moduleGraph.getModuleById(PRACHT_DEV_MODULE_ID);
2601
+ if (devMod) server.moduleGraph.invalidateModule(devMod);
2385
2602
  if (relative.startsWith(resolved.routesDir)) {
2386
2603
  const clientMod = server.moduleGraph.getModuleById(PRACHT_CLIENT_MODULE_ID);
2387
2604
  if (clientMod) server.moduleGraph.invalidateModule(clientMod);
@@ -2520,8 +2737,10 @@ function watchPagesDirectory(server, resolved, root) {
2520
2737
  function invalidateVirtualModules(server) {
2521
2738
  const clientMod = server.moduleGraph.getModuleById(PRACHT_CLIENT_MODULE_ID);
2522
2739
  const serverMod = server.moduleGraph.getModuleById(PRACHT_SERVER_MODULE_ID);
2740
+ const devMod = server.moduleGraph.getModuleById(PRACHT_DEV_MODULE_ID);
2523
2741
  if (clientMod) server.moduleGraph.invalidateModule(clientMod);
2524
2742
  if (serverMod) server.moduleGraph.invalidateModule(serverMod);
2743
+ if (devMod) server.moduleGraph.invalidateModule(devMod);
2525
2744
  }
2526
2745
  const ROUTE_FILE_EXTENSIONS = new Set([
2527
2746
  ".ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pracht/vite-plugin",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Vite plugin for Pracht apps with virtual modules, dev SSR, prerendering, route inspection, and multi-adapter builds.",
5
5
  "keywords": [
6
6
  "pracht",
@@ -44,10 +44,10 @@
44
44
  "dependencies": {
45
45
  "@preact/preset-vite": "^2.10.5",
46
46
  "@prefresh/vite": "^2.0.0",
47
- "@pracht/adapter-node": "0.3.3",
48
- "@pracht/core": "0.11.0",
49
- "@pracht/preact-ssr-precompile": "0.1.2",
50
- "@pracht/capabilities": "0.1.0"
47
+ "@pracht/adapter-node": "0.3.4",
48
+ "@pracht/core": "0.11.1",
49
+ "@pracht/capabilities": "0.1.0",
50
+ "@pracht/preact-ssr-precompile": "0.1.2"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "vite": "^8.0.0"