@pracht/vite-plugin 0.7.1 → 0.7.3
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.mjs +74 -52
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -5,8 +5,8 @@ import preact from "@preact/preset-vite";
|
|
|
5
5
|
import { dirname, extname, join, resolve } from "node:path";
|
|
6
6
|
import { loadEnv, parseAst } from "vite";
|
|
7
7
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
8
|
-
import { CAPABILITY_SETTLED_EVENT, CAPABILITY_TRANSPORT_HEADER, CONFIRMATION_HEADER
|
|
9
|
-
import {
|
|
8
|
+
import { CAPABILITY_SETTLED_EVENT, CAPABILITY_TRANSPORT_HEADER, CONFIRMATION_HEADER } from "@pracht/capabilities";
|
|
9
|
+
import { extractCapabilityProjection, extractCapabilityRegistrations } from "@pracht/capabilities/static";
|
|
10
10
|
import { createNodeServerEntryModule } from "@pracht/adapter-node";
|
|
11
11
|
import { resolveRegistryModule } from "@pracht/core";
|
|
12
12
|
//#region src/client-module-query.ts
|
|
@@ -1219,6 +1219,32 @@ function createEnvSafetyPlugin(envSafety) {
|
|
|
1219
1219
|
};
|
|
1220
1220
|
}
|
|
1221
1221
|
//#endregion
|
|
1222
|
+
//#region src/hot-update-reload.ts
|
|
1223
|
+
/**
|
|
1224
|
+
* True when `file` participates in server rendering but has no counterpart in
|
|
1225
|
+
* the client module graph, meaning client HMR can never deliver its change.
|
|
1226
|
+
*/
|
|
1227
|
+
function isServerOnlyModuleFile(server, file) {
|
|
1228
|
+
const environments = server.environments;
|
|
1229
|
+
const client = environments?.client;
|
|
1230
|
+
if (!client) return false;
|
|
1231
|
+
if (hasModules(client, file)) return false;
|
|
1232
|
+
for (const [name, environment] of Object.entries(environments ?? {})) {
|
|
1233
|
+
if (name === "client" || !environment) continue;
|
|
1234
|
+
if (hasModules(environment, file)) return true;
|
|
1235
|
+
}
|
|
1236
|
+
return false;
|
|
1237
|
+
}
|
|
1238
|
+
/** Reload open pages when `file` can only reach them through the server. */
|
|
1239
|
+
function sendServerOnlyFullReload(server, file) {
|
|
1240
|
+
if (!isServerOnlyModuleFile(server, file)) return false;
|
|
1241
|
+
server.environments?.client?.hot?.send({ type: "full-reload" });
|
|
1242
|
+
return true;
|
|
1243
|
+
}
|
|
1244
|
+
function hasModules(environment, file) {
|
|
1245
|
+
return (environment.moduleGraph.getModulesByFile(file)?.size ?? 0) > 0;
|
|
1246
|
+
}
|
|
1247
|
+
//#endregion
|
|
1222
1248
|
//#region src/plugin-assets.ts
|
|
1223
1249
|
const PRACHT_CLIENT_MODULE_ID = "virtual:pracht/client";
|
|
1224
1250
|
const PRACHT_SERVER_MODULE_ID = "virtual:pracht/server";
|
|
@@ -1415,57 +1441,37 @@ function extractCapabilities(options = {}, root = process.cwd()) {
|
|
|
1415
1441
|
return extractCapabilityMetadata(name, file, source);
|
|
1416
1442
|
});
|
|
1417
1443
|
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
const
|
|
1433
|
-
if (
|
|
1434
|
-
const
|
|
1435
|
-
let
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
if (webmcp && !httpPath) throw new Error(`[pracht] Capability "${name}" (${file}): expose.webmcp requires expose.http.`);
|
|
1441
|
-
let description = "";
|
|
1442
|
-
const descriptionText = properties.get("description");
|
|
1443
|
-
if (descriptionText) {
|
|
1444
|
-
const value = evaluateLiteral(descriptionText);
|
|
1445
|
-
if (typeof value === "string") description = value;
|
|
1446
|
-
}
|
|
1447
|
-
let effect = null;
|
|
1448
|
-
const effectText = properties.get("effect");
|
|
1449
|
-
if (effectText) {
|
|
1450
|
-
const value = evaluateLiteral(effectText);
|
|
1451
|
-
if (typeof value === "string") effect = value;
|
|
1452
|
-
}
|
|
1453
|
-
if (httpPath && effect !== "read" && effect !== "write" && effect !== "destructive") throw new Error(`[pracht] Capability "${name}" (${file}) is exposed via HTTP, but its "effect" could not be extracted at build time. HTTP-exposed capabilities must declare "effect" as an inline "read", "write", or "destructive" string literal.`);
|
|
1454
|
-
let inputSchema = null;
|
|
1455
|
-
if (webmcp) {
|
|
1456
|
-
const inputText = properties.get("input");
|
|
1457
|
-
const value = inputText ? evaluateLiteral(inputText) : void 0;
|
|
1458
|
-
if (!isPlainObject(value)) throw new Error(`[pracht] Capability "${name}" (${file}) is exposed via WebMCP, but its "input" schema could not be extracted at build time. WebMCP-exposed capabilities must declare their input schema as an inline object literal.`);
|
|
1459
|
-
inputSchema = value;
|
|
1444
|
+
/**
|
|
1445
|
+
* Absolute paths of the capability modules the manifest registers.
|
|
1446
|
+
*
|
|
1447
|
+
* The client-import guard uses this rather than a `capabilitiesDir` prefix
|
|
1448
|
+
* test: registration is what makes a module server-only, and the manifest may
|
|
1449
|
+
* point anywhere. A directory test both misses capabilities registered from
|
|
1450
|
+
* elsewhere and wrongly rejects ordinary co-located files (shared constants,
|
|
1451
|
+
* types) that happen to sit in the capability folder.
|
|
1452
|
+
*
|
|
1453
|
+
* Returns an empty list when the manifest cannot be read or parsed — the
|
|
1454
|
+
* virtual-module generation raises its own precise error for those, and
|
|
1455
|
+
* guessing here would turn one clear failure into two confusing ones.
|
|
1456
|
+
*/
|
|
1457
|
+
function resolveCapabilityModulePaths(options = {}, root = process.cwd()) {
|
|
1458
|
+
const resolved = resolveOptions(options);
|
|
1459
|
+
if (resolved.pagesDir) return [];
|
|
1460
|
+
const appFileAbs = resolve(root, resolved.appFile.replace(/^\//, ""));
|
|
1461
|
+
let manifestSource;
|
|
1462
|
+
try {
|
|
1463
|
+
manifestSource = readFileSync(appFileAbs, "utf-8");
|
|
1464
|
+
} catch {
|
|
1465
|
+
return [];
|
|
1460
1466
|
}
|
|
1467
|
+
const appDir = dirname(appFileAbs);
|
|
1468
|
+
return extractCapabilityRegistrations(manifestSource).map(({ file }) => file.startsWith("/") ? resolve(root, file.replace(/^\//, "")) : resolve(appDir, file));
|
|
1469
|
+
}
|
|
1470
|
+
function extractCapabilityMetadata(name, file, source) {
|
|
1461
1471
|
return {
|
|
1462
1472
|
name,
|
|
1463
1473
|
file,
|
|
1464
|
-
|
|
1465
|
-
effect,
|
|
1466
|
-
httpPath,
|
|
1467
|
-
webmcp,
|
|
1468
|
-
inputSchema
|
|
1474
|
+
...extractCapabilityProjection(name, source, (detail) => `[pracht] Capability ${JSON.stringify(name)} (${file}) ${detail}`)
|
|
1469
1475
|
};
|
|
1470
1476
|
}
|
|
1471
1477
|
/**
|
|
@@ -1643,9 +1649,6 @@ function hasWebmcpCapabilities(options = {}, root = process.cwd()) {
|
|
|
1643
1649
|
return true;
|
|
1644
1650
|
}
|
|
1645
1651
|
}
|
|
1646
|
-
function isPlainObject(value) {
|
|
1647
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1648
|
-
}
|
|
1649
1652
|
//#endregion
|
|
1650
1653
|
//#region src/plugin-codegen.ts
|
|
1651
1654
|
const ROUTE_MODULE_EXTENSIONS = new Set([
|
|
@@ -2484,6 +2487,7 @@ function pracht(options = {}) {
|
|
|
2484
2487
|
const isPagesMode = !!resolved.pagesDir;
|
|
2485
2488
|
let root = process.cwd();
|
|
2486
2489
|
let routeFileDirs = [];
|
|
2490
|
+
let capabilityModulePaths = /* @__PURE__ */ new Set();
|
|
2487
2491
|
if (isPagesMode && options.appFile) console.warn("[pracht] Both `pagesDir` and `appFile` are set. `pagesDir` takes precedence — `appFile` will be ignored.");
|
|
2488
2492
|
let isBuild = false;
|
|
2489
2493
|
const prachtPlugin = {
|
|
@@ -2519,6 +2523,7 @@ function pracht(options = {}) {
|
|
|
2519
2523
|
root = config.root;
|
|
2520
2524
|
isBuild = config.command === "build";
|
|
2521
2525
|
routeFileDirs = computeRouteFileDirs(root, resolved);
|
|
2526
|
+
capabilityModulePaths = new Set(resolveCapabilityModulePaths(resolved, root).map((path) => toPosixPath(path)));
|
|
2522
2527
|
},
|
|
2523
2528
|
resolveId(id, importer, resolveIdOptions) {
|
|
2524
2529
|
if (isIslandsClientModule(id)) return PRACHT_ISLANDS_CLIENT_MODULE_ID;
|
|
@@ -2580,6 +2585,7 @@ function pracht(options = {}) {
|
|
|
2580
2585
|
if (isPagesMode && relative.startsWith(resolved.pagesDir)) {
|
|
2581
2586
|
clearPagesAppSourceCache();
|
|
2582
2587
|
invalidateVirtualModules(server);
|
|
2588
|
+
sendServerOnlyFullReload(server, file);
|
|
2583
2589
|
return;
|
|
2584
2590
|
}
|
|
2585
2591
|
if (!isPagesMode && relative === resolved.appFile) {
|
|
@@ -2617,12 +2623,14 @@ function pracht(options = {}) {
|
|
|
2617
2623
|
if (capabilityMod) server.moduleGraph.invalidateModule(capabilityMod);
|
|
2618
2624
|
}
|
|
2619
2625
|
}
|
|
2626
|
+
sendServerOnlyFullReload(server, file);
|
|
2620
2627
|
}
|
|
2621
2628
|
};
|
|
2622
2629
|
const clientModuleTransformPlugin = {
|
|
2623
2630
|
name: "pracht:client-module-transform",
|
|
2624
2631
|
enforce: "post",
|
|
2625
2632
|
transform(code, id, transformOptions) {
|
|
2633
|
+
if (!transformOptions?.ssr && isCapabilityModule(id, capabilityModulePaths)) throw new Error(`[pracht] Capability module ${JSON.stringify(toPosixPath(id))} was imported by client code. Capability modules are server-only — their run() implementation and its imports would be bundled for every visitor. Call the capability instead: \`callCapability\`/\`capabilities\` from "virtual:pracht/capabilities" in the browser, or \`invokeCapability\` from "@pracht/core/server" in loaders, middleware, and API routes.`);
|
|
2626
2634
|
if (!(isPrachtClientModuleId(id) || !transformOptions?.ssr && isRouteOrShellFile(id, routeFileDirs))) return null;
|
|
2627
2635
|
const transformed = stripServerOnlyExportsForClient(code, id);
|
|
2628
2636
|
if (transformed === code) return null;
|
|
@@ -2754,6 +2762,20 @@ const ROUTE_FILE_EXTENSIONS = new Set([
|
|
|
2754
2762
|
function computeRouteFileDirs(root, resolved) {
|
|
2755
2763
|
return (resolved.pagesDir ? [resolved.pagesDir] : [resolved.routesDir, resolved.shellsDir]).map((dir) => resolveConfigPath(root, dir)).map(withTrailingSep);
|
|
2756
2764
|
}
|
|
2765
|
+
/**
|
|
2766
|
+
* Whether `id` is one of the capability modules the manifest registers.
|
|
2767
|
+
* Matching the registered set rather than a directory keeps ordinary files that
|
|
2768
|
+
* merely live beside capabilities importable, and still catches a capability
|
|
2769
|
+
* registered from anywhere else in the project. Extension-agnostic, because the
|
|
2770
|
+
* comparison is against paths the manifest already resolved.
|
|
2771
|
+
*/
|
|
2772
|
+
function isCapabilityModule(id, capabilityModulePaths) {
|
|
2773
|
+
if (capabilityModulePaths.size === 0) return false;
|
|
2774
|
+
const queryStart = id.indexOf("?");
|
|
2775
|
+
const path = queryStart === -1 ? id : id.slice(0, queryStart);
|
|
2776
|
+
if (path.startsWith("\0") || path.startsWith("virtual:")) return false;
|
|
2777
|
+
return capabilityModulePaths.has(toPosixPath(path));
|
|
2778
|
+
}
|
|
2757
2779
|
function isRouteOrShellFile(id, dirs) {
|
|
2758
2780
|
if (dirs.length === 0) return false;
|
|
2759
2781
|
const queryStart = id.indexOf("?");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pracht/vite-plugin",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
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,9 +44,9 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@preact/preset-vite": "^2.10.5",
|
|
46
46
|
"@prefresh/vite": "^2.0.0",
|
|
47
|
-
"@pracht/adapter-node": "0.3.
|
|
48
|
-
"@pracht/
|
|
49
|
-
"@pracht/
|
|
47
|
+
"@pracht/adapter-node": "0.3.6",
|
|
48
|
+
"@pracht/capabilities": "0.1.1",
|
|
49
|
+
"@pracht/core": "0.11.3",
|
|
50
50
|
"@pracht/preact-ssr-precompile": "0.1.2"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|