@pyreon/vite-plugin 0.19.0 → 0.21.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.
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
5386
5386
  </script>
5387
5387
  <script>
5388
5388
  /*<!--*/
5389
- const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src/index.ts","uid":"88d6c780-1"}]}],"isRoot":true},"nodeParts":{"88d6c780-1":{"renderedLength":29326,"gzipLength":9623,"brotliLength":0,"metaUid":"88d6c780-0"}},"nodeMetas":{"88d6c780-0":{"id":"/src/index.ts","moduleParts":{"index.js":"88d6c780-1"},"imported":[{"uid":"88d6c780-2"},{"uid":"88d6c780-3"},{"uid":"88d6c780-4"}],"importedBy":[],"isEntry":true},"88d6c780-2":{"id":"node:fs","moduleParts":{},"imported":[],"importedBy":[{"uid":"88d6c780-0"}]},"88d6c780-3":{"id":"node:path","moduleParts":{},"imported":[],"importedBy":[{"uid":"88d6c780-0"}]},"88d6c780-4":{"id":"@pyreon/compiler","moduleParts":{},"imported":[],"importedBy":[{"uid":"88d6c780-0"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src/index.ts","uid":"f3fd0c0b-1"}]},{"name":"rocketstyle-collapse-C4eMAnwR.js","children":[{"name":"src/rocketstyle-collapse.ts","uid":"f3fd0c0b-3"}]}],"isRoot":true},"nodeParts":{"f3fd0c0b-1":{"renderedLength":33323,"gzipLength":10763,"brotliLength":0,"metaUid":"f3fd0c0b-0"},"f3fd0c0b-3":{"renderedLength":3424,"gzipLength":1530,"brotliLength":0,"metaUid":"f3fd0c0b-2"}},"nodeMetas":{"f3fd0c0b-0":{"id":"/src/index.ts","moduleParts":{"index.js":"f3fd0c0b-1"},"imported":[{"uid":"f3fd0c0b-4"},{"uid":"f3fd0c0b-5"},{"uid":"f3fd0c0b-6"},{"uid":"f3fd0c0b-2","dynamic":true}],"importedBy":[],"isEntry":true},"f3fd0c0b-2":{"id":"/src/rocketstyle-collapse.ts","moduleParts":{"rocketstyle-collapse-C4eMAnwR.js":"f3fd0c0b-3"},"imported":[{"uid":"f3fd0c0b-7","dynamic":true}],"importedBy":[{"uid":"f3fd0c0b-0"}]},"f3fd0c0b-4":{"id":"node:fs","moduleParts":{},"imported":[],"importedBy":[{"uid":"f3fd0c0b-0"}]},"f3fd0c0b-5":{"id":"node:path","moduleParts":{},"imported":[],"importedBy":[{"uid":"f3fd0c0b-0"}]},"f3fd0c0b-6":{"id":"@pyreon/compiler","moduleParts":{},"imported":[],"importedBy":[{"uid":"f3fd0c0b-0"}]},"f3fd0c0b-7":{"id":"vite","moduleParts":{},"imported":[],"importedBy":[{"uid":"f3fd0c0b-2"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5390
5390
 
5391
5391
  const run = () => {
5392
5392
  const width = window.innerWidth;
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
- import { generateContext, transformDeferInline, transformJSX } from "@pyreon/compiler";
3
+ import { generateContext, scanCollapsibleSites, transformDeferInline, transformJSX } from "@pyreon/compiler";
4
4
 
5
5
  //#region src/index.ts
6
6
  /**
@@ -36,6 +36,11 @@ import { generateContext, transformDeferInline, transformJSX } from "@pyreon/com
36
36
  * vite build # client bundle
37
37
  * vite build --ssr src/entry-server.ts --outDir dist/server # server bundle
38
38
  */
39
+ let _createCollapseResolver = null;
40
+ async function loadCreateCollapseResolver() {
41
+ if (!_createCollapseResolver) _createCollapseResolver = (await import("./rocketstyle-collapse-C4eMAnwR.js")).createCollapseResolver;
42
+ return _createCollapseResolver;
43
+ }
39
44
  const HMR_RUNTIME_ID = "\0pyreon/hmr-runtime";
40
45
  const HMR_RUNTIME_IMPORT = "virtual:pyreon/hmr-runtime";
41
46
  const ISLANDS_REGISTRY_ID = "\0pyreon/islands-registry";
@@ -64,6 +69,13 @@ const COMPAT_ALIASES = {
64
69
  "solid-js": "@pyreon/solid-compat",
65
70
  "solid-js/jsx-runtime": "@pyreon/solid-compat/jsx-runtime",
66
71
  "solid-js/jsx-dev-runtime": "@pyreon/solid-compat/jsx-runtime"
72
+ },
73
+ svelte: {
74
+ svelte: "@pyreon/svelte-compat",
75
+ "svelte/store": "@pyreon/svelte-compat/store",
76
+ "svelte/internal": "@pyreon/svelte-compat",
77
+ "svelte/jsx-runtime": "@pyreon/svelte-compat/jsx-runtime",
78
+ "svelte/jsx-dev-runtime": "@pyreon/svelte-compat/jsx-runtime"
67
79
  }
68
80
  };
69
81
  /**
@@ -132,6 +144,7 @@ function getCompatTarget(compat, id) {
132
144
  if (compat === "preact") return "@pyreon/preact-compat/jsx-runtime";
133
145
  if (compat === "vue") return "@pyreon/vue-compat/jsx-runtime";
134
146
  if (compat === "solid") return "@pyreon/solid-compat/jsx-runtime";
147
+ if (compat === "svelte") return "@pyreon/svelte-compat/jsx-runtime";
135
148
  }
136
149
  }
137
150
  /**
@@ -164,7 +177,44 @@ function pyreonPlugin(options) {
164
177
  const ssrConfig = options?.ssr;
165
178
  const compat = options?.compat;
166
179
  const islandsEnabled = options?.islands !== false;
180
+ const collapseOpt = options?.collapse;
181
+ const collapseEnabled = collapseOpt === true || collapseOpt != null && collapseOpt !== false;
182
+ const collapseUserCfg = collapseOpt && collapseOpt !== true ? collapseOpt : {};
183
+ const collapseProvider = collapseUserCfg.provider ?? {
184
+ name: "PyreonUI",
185
+ source: "@pyreon/ui-core"
186
+ };
187
+ const collapseTheme = collapseUserCfg.theme ?? {
188
+ name: "theme",
189
+ source: "@pyreon/ui-theme"
190
+ };
191
+ const collapseMode = collapseUserCfg.mode ?? {
192
+ name: "useMode",
193
+ source: "@pyreon/ui-core"
194
+ };
195
+ const collapseSources = new Set(collapseUserCfg.sources ?? ["@pyreon/ui-components"]);
196
+ const collapseComponentFilter = collapseUserCfg.components ? (n) => collapseUserCfg.components.includes(n) : null;
197
+ let collapseResolver = null;
198
+ let collapseResolverInit = null;
199
+ /**
200
+ * Lazily spin ONE programmatic Vite SSR server (bound to the project's
201
+ * own vite config) the first time a client-graph module actually has a
202
+ * collapsible call site. Memoized via `collapseResolverInit` so
203
+ * concurrent transforms share the single server. Returns null if the
204
+ * server fails to start (graceful — every call site then keeps its
205
+ * normal rocketstyle mount).
206
+ */
207
+ function ensureCollapseResolver() {
208
+ if (collapseResolver) return Promise.resolve(collapseResolver);
209
+ if (collapseResolverInit) return collapseResolverInit;
210
+ collapseResolverInit = loadCreateCollapseResolver().then((create) => create(projectRoot)).then((r) => {
211
+ collapseResolver = r;
212
+ return r;
213
+ }).catch(() => null);
214
+ return collapseResolverInit;
215
+ }
167
216
  let isBuild = false;
217
+ let warnedDevCollapse = false;
168
218
  let projectRoot = "";
169
219
  const signalExportRegistry = /* @__PURE__ */ new Map();
170
220
  const resolveCache = /* @__PURE__ */ new Map();
@@ -195,6 +245,13 @@ function pyreonPlugin(options) {
195
245
  await prescanSignalExports(projectRoot, signalExportRegistry);
196
246
  if (islandsEnabled) await prescanIslandDeclarations(projectRoot, islandRegistry);
197
247
  },
248
+ async closeBundle() {
249
+ if (collapseResolver) {
250
+ await collapseResolver.dispose();
251
+ collapseResolver = null;
252
+ collapseResolverInit = null;
253
+ }
254
+ },
198
255
  async resolveId(id, importer) {
199
256
  if (id === HMR_RUNTIME_IMPORT) return HMR_RUNTIME_ID;
200
257
  if (id === ISLANDS_REGISTRY_IMPORT) return ISLANDS_REGISTRY_ID;
@@ -210,7 +267,7 @@ function pyreonPlugin(options) {
210
267
  async transform(code, id, transformOptions) {
211
268
  const ext = getExt(id);
212
269
  if (ext !== ".tsx" && ext !== ".jsx" && ext !== ".pyreon") return;
213
- if (compat === "react" || compat === "preact" || compat === "vue" || compat === "solid") {
270
+ if (compat === "react" || compat === "preact" || compat === "vue" || compat === "solid" || compat === "svelte") {
214
271
  if (compat === "react" || compat === "preact") {
215
272
  const transformed = transformCompatAttributes(code);
216
273
  if (transformed !== code) return {
@@ -226,9 +283,55 @@ function pyreonPlugin(options) {
226
283
  const sourceForJsx = deferResult.changed ? deferResult.code : code;
227
284
  for (const w of deferResult.warnings) this.warn(`${w.message} (${id}:${w.line}:${w.column})`);
228
285
  const knownSignals = await resolveImportedSignals(sourceForJsx, id, signalExportRegistry, this, resolveCache);
286
+ const isSsr = transformOptions?.ssr === true;
287
+ let collapseRocketstyle;
288
+ if (collapseEnabled && !isBuild && !isSsr && !warnedDevCollapse) {
289
+ warnedDevCollapse = true;
290
+ this.info("[Pyreon] collapse is build-only — `vite dev` keeps the normal rocketstyle mount so theme-source edits stay HMR-reactive. Production `vite build` collapses the literal-prop sites.");
291
+ }
292
+ if (collapseEnabled && isBuild && !isSsr) {
293
+ const scanned = scanCollapsibleSites(sourceForJsx, id, collapseSources).filter((s) => !collapseComponentFilter || collapseComponentFilter(s.componentName));
294
+ if (scanned.length > 0) {
295
+ const resolver = await ensureCollapseResolver();
296
+ if (resolver) {
297
+ const sites = /* @__PURE__ */ new Map();
298
+ const candidates = /* @__PURE__ */ new Set();
299
+ for (const s of scanned) {
300
+ const resolved = await resolver.resolve({
301
+ component: {
302
+ name: s.importedName,
303
+ source: s.source
304
+ },
305
+ props: s.props,
306
+ childrenText: s.childrenText,
307
+ config: {
308
+ provider: collapseProvider,
309
+ theme: collapseTheme,
310
+ mode: collapseMode
311
+ }
312
+ });
313
+ if (!resolved) continue;
314
+ candidates.add(s.componentName);
315
+ sites.set(s.key, {
316
+ templateHtml: resolved.templateHtml,
317
+ lightClass: resolved.lightClass,
318
+ darkClass: resolved.darkClass,
319
+ rules: resolved.rules,
320
+ ruleKey: resolved.key
321
+ });
322
+ }
323
+ if (sites.size > 0) collapseRocketstyle = {
324
+ candidates,
325
+ sites,
326
+ mode: collapseMode
327
+ };
328
+ }
329
+ }
330
+ }
229
331
  const result = transformJSX(sourceForJsx, id, {
230
- ssr: transformOptions?.ssr === true,
231
- knownSignals
332
+ ssr: isSsr,
333
+ knownSignals,
334
+ ...collapseRocketstyle ? { collapseRocketstyle } : {}
232
335
  });
233
336
  for (const w of result.warnings) this.warn(`${w.message} (${id}:${w.line}:${w.column})`);
234
337
  let output = result.code;
@@ -238,7 +341,7 @@ function pyreonPlugin(options) {
238
341
  }
239
342
  return {
240
343
  code: output,
241
- map: null
344
+ map: result.map ?? null
242
345
  };
243
346
  },
244
347
  configureServer(server) {
@@ -0,0 +1,113 @@
1
+ //#region src/rocketstyle-collapse.ts
2
+ function fnv1a(str) {
3
+ let h = 2166136261;
4
+ for (let i = 0; i < str.length; i++) {
5
+ h ^= str.charCodeAt(i);
6
+ h = Math.imul(h, 16777619);
7
+ }
8
+ return (h >>> 0).toString(36);
9
+ }
10
+ const FIRST_CLASS_RE = /^(\s*<[a-zA-Z][\w-]*)([^>]*?)\sclass="([^"]*)"([^>]*>)/;
11
+ /** Strip the FIRST element's `class="..."`, returning [stripped, class]. */
12
+ function stripRootClass(html) {
13
+ const m = FIRST_CLASS_RE.exec(html);
14
+ if (!m) return null;
15
+ return {
16
+ stripped: html.replace(FIRST_CLASS_RE, "$1$2$4"),
17
+ cls: m[3] ?? ""
18
+ };
19
+ }
20
+ /**
21
+ * Pure extraction half — given the two rendered HTML strings and the
22
+ * styler rule snapshot, derive the ResolvedCollapse (or null on a shape
23
+ * the slice doesn't collapse). Separated for direct unit-testing without
24
+ * spinning Vite.
25
+ */
26
+ function deriveCollapse(lightHtml, darkHtml, rules) {
27
+ const light = stripRootClass(lightHtml);
28
+ const dark = stripRootClass(darkHtml);
29
+ if (!light || !dark || !light.cls || !dark.cls) return null;
30
+ if (light.stripped !== dark.stripped) return null;
31
+ return {
32
+ templateHtml: light.stripped.trim(),
33
+ lightClass: light.cls,
34
+ darkClass: dark.cls,
35
+ rules,
36
+ key: fnv1a(rules.join("\0"))
37
+ };
38
+ }
39
+ /**
40
+ * Create a resolver backed by ONE programmatic Vite SSR server bound to
41
+ * `projectRoot`'s vite config. Reused across every call site in a build;
42
+ * `dispose()` at buildEnd. Module loads are cached by Vite's own SSR
43
+ * module graph (provider/theme/component import once).
44
+ */
45
+ async function createCollapseResolver(projectRoot) {
46
+ const { createServer } = await import("vite");
47
+ let server = await createServer({
48
+ root: projectRoot,
49
+ server: { middlewareMode: true },
50
+ appType: "custom",
51
+ logLevel: "silent",
52
+ optimizeDeps: {
53
+ noDiscovery: true,
54
+ include: []
55
+ }
56
+ });
57
+ const cache = /* @__PURE__ */ new Map();
58
+ async function load(spec) {
59
+ return await server.ssrLoadModule(spec);
60
+ }
61
+ return {
62
+ async resolve(input) {
63
+ const ck = JSON.stringify([
64
+ input.component,
65
+ input.props,
66
+ input.childrenText,
67
+ input.config.provider,
68
+ input.config.theme
69
+ ]);
70
+ if (cache.has(ck)) return cache.get(ck) ?? null;
71
+ try {
72
+ if (!server) return null;
73
+ const rs = await load("@pyreon/runtime-server");
74
+ const core = await load("@pyreon/core");
75
+ const styler = await load("@pyreon/styler");
76
+ const prov = await load(input.config.provider.source);
77
+ const thm = await load(input.config.theme.source);
78
+ const comp = await load(input.component.source);
79
+ const renderToString = rs.renderToString;
80
+ const h = core.h;
81
+ const Provider = prov[input.config.provider.name];
82
+ const themeVal = thm[input.config.theme.name];
83
+ const Component = comp[input.component.name];
84
+ const sheet = styler.sheet;
85
+ if (typeof Component !== "function" || Provider == null || themeVal == null) {
86
+ cache.set(ck, null);
87
+ return null;
88
+ }
89
+ const childArgs = input.childrenText ? [input.childrenText] : [];
90
+ const node = (mode) => h(Provider, {
91
+ theme: themeVal,
92
+ mode
93
+ }, h(Component, input.props, ...childArgs));
94
+ const result = deriveCollapse(await renderToString(node("light")), await renderToString(node("dark")), sheet.getStyleRules().slice());
95
+ cache.set(ck, result);
96
+ return result;
97
+ } catch {
98
+ cache.set(ck, null);
99
+ return null;
100
+ }
101
+ },
102
+ async dispose() {
103
+ const s = server;
104
+ server = null;
105
+ cache.clear();
106
+ if (s) await s.close();
107
+ }
108
+ };
109
+ }
110
+
111
+ //#endregion
112
+ export { createCollapseResolver };
113
+ //# sourceMappingURL=rocketstyle-collapse-C4eMAnwR.js.map
@@ -1,7 +1,7 @@
1
1
  import { Plugin } from "vite";
2
2
 
3
3
  //#region src/index.d.ts
4
- type CompatFramework = 'react' | 'preact' | 'vue' | 'solid';
4
+ type CompatFramework = 'react' | 'preact' | 'vue' | 'solid' | 'svelte';
5
5
  interface PyreonPluginOptions {
6
6
  /**
7
7
  * Alias imports from an existing framework to Pyreon's compat layer.
@@ -13,6 +13,7 @@ interface PyreonPluginOptions {
13
13
  * pyreon({ compat: "react" }) // react + react-dom → @pyreon/react-compat
14
14
  * pyreon({ compat: "vue" }) // vue → @pyreon/vue-compat
15
15
  * pyreon({ compat: "solid" }) // solid-js → @pyreon/solid-compat
16
+ * pyreon({ compat: "svelte" }) // svelte + svelte/store → @pyreon/svelte-compat
16
17
  * pyreon({ compat: "preact" }) // preact + hooks + signals → @pyreon/preact-compat
17
18
  */
18
19
  compat?: CompatFramework;
@@ -55,8 +56,54 @@ interface PyreonPluginOptions {
55
56
  * hydrateIslandsAuto()
56
57
  */
57
58
  islands?: boolean;
59
+ /**
60
+ * P0 — opt-in compile-time rocketstyle wrapper collapse. `true` uses
61
+ * the default provider/theme/mode wiring (PyreonUI + theme +
62
+ * useMode from @pyreon/ui-core / @pyreon/ui-theme). Pass an object to
63
+ * override. OFF by default (zero behaviour change). When on, the
64
+ * plugin SSR-resolves every literal-prop call site of a candidate
65
+ * component (real component, light + dark) and the compiler collapses
66
+ * the 5-layer wrapper mount into a single `_rsCollapse` cloneNode.
67
+ * Only the CLIENT graph is collapsed — the SSR graph keeps the normal
68
+ * mount (and the resolver itself uses SSR render).
69
+ *
70
+ * @example pyreon({ collapse: true })
71
+ * @example pyreon({ collapse: { components: ['Button', 'Badge'] } })
72
+ */
73
+ collapse?: boolean | PyreonCollapseOptions;
74
+ }
75
+ interface PyreonCollapseOptions {
76
+ /**
77
+ * Import sources whose components may collapse. Default:
78
+ * `['@pyreon/ui-components']`. The compiler's AST scan only considers
79
+ * a call site whose component was imported from one of these sources;
80
+ * the conservative bail catalogue + the SSR resolver are the real
81
+ * gate beyond that.
82
+ */
83
+ sources?: string[];
84
+ /**
85
+ * Optional local-name allowlist applied AFTER the source scan
86
+ * (e.g. `['Button']`). Omit to collapse every collapsible component
87
+ * from the configured sources.
88
+ */
89
+ components?: string[];
90
+ /** Override the theme/mode provider. Default PyreonUI@@pyreon/ui-core. */
91
+ provider?: {
92
+ name: string;
93
+ source: string;
94
+ };
95
+ /** Override the theme object. Default theme@@pyreon/ui-theme. */
96
+ theme?: {
97
+ name: string;
98
+ source: string;
99
+ };
100
+ /** Override the live mode accessor. Default useMode@@pyreon/ui-core. */
101
+ mode?: {
102
+ name: string;
103
+ source: string;
104
+ };
58
105
  }
59
106
  declare function pyreonPlugin(options?: PyreonPluginOptions): Plugin;
60
107
  //#endregion
61
- export { CompatFramework, PyreonPluginOptions, pyreonPlugin as default };
108
+ export { CompatFramework, PyreonCollapseOptions, PyreonPluginOptions, pyreonPlugin as default };
62
109
  //# sourceMappingURL=index2.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/vite-plugin",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "Vite plugin for Pyreon — .pyreon SFC support, HMR, compiler integration",
5
5
  "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/vite-plugin#readme",
6
6
  "bugs": {
@@ -43,7 +43,7 @@
43
43
  "prepublishOnly": "bun run build"
44
44
  },
45
45
  "dependencies": {
46
- "@pyreon/compiler": "^0.19.0"
46
+ "@pyreon/compiler": "^0.21.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "vite": "^8.0.0"