@ripple-ts/vite-plugin 0.3.10 → 0.3.12

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 CHANGED
@@ -1,5 +1,26 @@
1
1
  # @ripple-ts/vite-plugin
2
2
 
3
+ ## 0.3.12
4
+
5
+ ### Patch Changes
6
+
7
+ - [#859](https://github.com/Ripple-TS/ripple/pull/859)
8
+ [`cdd31ba`](https://github.com/Ripple-TS/ripple/commit/cdd31ba4c07ce504b01d56533e19a6ba37879f5a)
9
+ Thanks [@trueadm](https://github.com/trueadm)! - Add first-phase `.tsrx` support
10
+ across the core Ripple tooling so Vite, Rollup, TypeScript, the language server,
11
+ Prettier, ESLint, and editor integrations accept both `.ripple` and `.tsrx`
12
+ files.
13
+
14
+ - Updated dependencies []:
15
+ - @ripple-ts/adapter@0.3.12
16
+
17
+ ## 0.3.11
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies []:
22
+ - @ripple-ts/adapter@0.3.11
23
+
3
24
  ## 0.3.10
4
25
 
5
26
  ### Patch Changes
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Vite plugin for Ripple",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.3.10",
6
+ "version": "0.3.12",
7
7
  "type": "module",
8
8
  "module": "src/index.js",
9
9
  "main": "src/index.js",
@@ -32,13 +32,13 @@
32
32
  "url": "https://github.com/Ripple-TS/ripple/issues"
33
33
  },
34
34
  "dependencies": {
35
- "@ripple-ts/adapter": "0.3.10"
35
+ "@ripple-ts/adapter": "0.3.12"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^24.3.0",
39
39
  "type-fest": "^5.4.4",
40
40
  "vite": "^8.0.0",
41
- "ripple": "0.3.10"
41
+ "ripple": "0.3.12"
42
42
  },
43
43
  "publishConfig": {
44
44
  "access": "public"
package/src/index.js CHANGED
@@ -27,9 +27,29 @@ import { patch_global_fetch, is_rpc_request, handle_rpc_request } from '@ripple-
27
27
 
28
28
  // Re-export route classes
29
29
  export { RenderRoute, ServerRoute } from './routes.js';
30
+ export {
31
+ getRippleConfigPath,
32
+ loadRippleConfig,
33
+ resolveRippleConfig,
34
+ rippleConfigExists,
35
+ } from './load-config.js';
30
36
 
31
37
  const VITE_FS_PREFIX = '/@fs/';
32
38
  const IS_WINDOWS = process.platform === 'win32';
39
+ const VIRTUAL_HYDRATE_ID = 'virtual:ripple-hydrate';
40
+ const RESOLVED_VIRTUAL_HYDRATE_ID = '\0virtual:ripple-hydrate';
41
+ const VIRTUAL_COMPAT_ID = 'virtual:ripple-compat';
42
+ const RESOLVED_VIRTUAL_COMPAT_ID = '\0virtual:ripple-compat';
43
+ const RIPPLE_EXTENSIONS = ['.ripple', '.tsrx'];
44
+ const RIPPLE_EXTENSION_PATTERN = /\.(?:ripple|tsrx)$/;
45
+
46
+ /**
47
+ * @param {string} file_name
48
+ * @returns {boolean}
49
+ */
50
+ function is_ripple_module_path(file_name) {
51
+ return RIPPLE_EXTENSIONS.some((extension) => file_name.endsWith(extension));
52
+ }
33
53
 
34
54
  // Dev server always runs in Node — use node:async_hooks as default runtime
35
55
  // If the user provides adapter.runtime in their config, that will be used instead.
@@ -67,6 +87,60 @@ function getDevAsyncContext(config) {
67
87
  return devAsyncContext;
68
88
  }
69
89
 
90
+ /**
91
+ * @param {ResolvedRippleConfig | null} config
92
+ * @returns {string}
93
+ */
94
+ function create_compat_virtual_module(config) {
95
+ const compat_entries = Object.entries(config?.compat ?? {});
96
+
97
+ if (compat_entries.length === 0) {
98
+ return `const compat = undefined;
99
+ globalThis.__RIPPLE_COMPAT__ = compat;
100
+ export { compat };
101
+ export default compat;
102
+ `;
103
+ }
104
+
105
+ const imports = [];
106
+ const properties = [];
107
+
108
+ for (let i = 0; i < compat_entries.length; i++) {
109
+ const [kind, entry] = compat_entries[i];
110
+ const local_name = `__ripple_compat_factory_${i}`;
111
+
112
+ if (entry.factory) {
113
+ imports.push(
114
+ `import { ${entry.factory} as ${local_name} } from ${JSON.stringify(entry.from)};`,
115
+ );
116
+ } else {
117
+ imports.push(`import ${local_name} from ${JSON.stringify(entry.from)};`);
118
+ }
119
+
120
+ properties.push(` ${JSON.stringify(kind)}: ${local_name}(),`);
121
+ }
122
+
123
+ return `${imports.join('\n')}
124
+
125
+ const compat = {
126
+ ${properties.join('\n')}
127
+ };
128
+
129
+ globalThis.__RIPPLE_COMPAT__ = compat;
130
+
131
+ export { compat };
132
+ export default compat;
133
+ `;
134
+ }
135
+
136
+ /**
137
+ * @param {ResolvedRippleConfig | null} config
138
+ * @returns {boolean}
139
+ */
140
+ function has_route_config(config) {
141
+ return (config?.router.routes.length ?? 0) > 0;
142
+ }
143
+
70
144
  /**
71
145
  * @param {string} filename
72
146
  * @param {ResolvedConfig['root']} root
@@ -112,9 +186,9 @@ function hasRippleSource(packageJsonPath, subpath = '.') {
112
186
  /** @type {PackageJson} */
113
187
  const pkgJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
114
188
 
115
- // Check if main/module/exports point to .ripple files
189
+ // Check if main/module/exports point to Ripple source files
116
190
  /** @param {string | undefined} p */
117
- const checkPath = (p) => p && typeof p === 'string' && p.endsWith('.ripple');
191
+ const checkPath = (p) => p && typeof p === 'string' && is_ripple_module_path(p);
118
192
 
119
193
  // Handle exports field (modern)
120
194
  if (pkgJson.exports) {
@@ -168,7 +242,7 @@ function hasRippleSource(packageJsonPath, subpath = '.') {
168
242
  }
169
243
  }
170
244
 
171
- // Last resort: scan the package directory for .ripple files
245
+ // Last resort: scan the package directory for Ripple source files
172
246
  const packageDir = packageJsonPath.replace('/package.json', '');
173
247
  return hasRippleFilesInDirectory(packageDir);
174
248
  } catch (e) {
@@ -177,7 +251,7 @@ function hasRippleSource(packageJsonPath, subpath = '.') {
177
251
  }
178
252
 
179
253
  /**
180
- * Recursively check if a directory contains any .ripple files
254
+ * Recursively check if a directory contains any Ripple source files
181
255
  * @param {string} dir
182
256
  * @param {number} [maxDepth=3]
183
257
  * @returns {boolean}
@@ -194,7 +268,7 @@ function hasRippleFilesInDirectory(dir, maxDepth = 3) {
194
268
  continue;
195
269
  }
196
270
 
197
- if (entry.isFile() && entry.name.endsWith('.ripple')) {
271
+ if (entry.isFile() && is_ripple_module_path(entry.name)) {
198
272
  return true;
199
273
  }
200
274
 
@@ -327,6 +401,18 @@ export function ripple(inlineOptions = {}) {
327
401
  /** @type {Set<string>} File paths (relative to root) of .ripple modules with #server blocks */
328
402
  const serverBlockModules = new Set();
329
403
 
404
+ /**
405
+ * @returns {Promise<ResolvedRippleConfig | null>}
406
+ */
407
+ async function get_current_ripple_config() {
408
+ if (loadedRippleConfig) return loadedRippleConfig;
409
+ if (rippleConfig) return rippleConfig;
410
+ if (!root || !rippleConfigExists(root)) return null;
411
+
412
+ loadedRippleConfig = await loadRippleConfig(root);
413
+ return loadedRippleConfig;
414
+ }
415
+
330
416
  /** @type {Plugin[]} */
331
417
  const plugins = [
332
418
  {
@@ -344,6 +430,12 @@ export function ripple(inlineOptions = {}) {
344
430
  const projectRoot = userConfig.root || process.cwd();
345
431
 
346
432
  if (rippleConfigExists(projectRoot)) {
433
+ loadedRippleConfig = await loadRippleConfig(projectRoot);
434
+
435
+ if (!has_route_config(loadedRippleConfig)) {
436
+ return null;
437
+ }
438
+
347
439
  const htmlInput = path.join(projectRoot, 'index.html');
348
440
  if (!fs.existsSync(htmlInput)) {
349
441
  throw new Error(
@@ -356,11 +448,9 @@ export function ripple(inlineOptions = {}) {
356
448
  '[@ripple-ts/vite-plugin] Detected ripple.config.ts — configuring client build',
357
449
  );
358
450
 
359
- // Load ripple.config.ts early so build options (e.g. minify) can
451
+ // The config was loaded above so build options (e.g. minify) can
360
452
  // influence the client build config returned from this hook.
361
- // The loaded config is cached and reused by
362
- // buildStart and closeBundle.
363
- loadedRippleConfig = await loadRippleConfig(projectRoot);
453
+ // The loaded config is cached and reused by buildStart/closeBundle.
364
454
 
365
455
  const outDir = loadedRippleConfig.build.outDir;
366
456
 
@@ -408,9 +498,11 @@ export function ripple(inlineOptions = {}) {
408
498
  }
409
499
 
410
500
  if (excludeRippleExternalModules) {
501
+ /** @type {string[]} */
502
+ const excluded = userConfig.optimizeDeps?.exclude || [];
411
503
  return {
412
504
  optimizeDeps: {
413
- exclude: userConfig.optimizeDeps?.exclude || [],
505
+ exclude: excluded,
414
506
  },
415
507
  };
416
508
  }
@@ -421,6 +513,7 @@ export function ripple(inlineOptions = {}) {
421
513
  detectedPackages.forEach((pkg) => {
422
514
  ripplePackages.add(pkg);
423
515
  });
516
+ /** @type {string[]} */
424
517
  const existingExclude = userConfig.optimizeDeps?.exclude || [];
425
518
  console.log('[@ripple-ts/vite-plugin] Scan complete. Found:', detectedPackages);
426
519
  console.log(
@@ -428,7 +521,9 @@ export function ripple(inlineOptions = {}) {
428
521
  existingExclude,
429
522
  );
430
523
  // Merge with existing exclude list
431
- const allExclude = [...new Set([...existingExclude, ...ripplePackages])];
524
+ const ripple_package_list = /** @type {string[]} */ (Array.from(ripplePackages));
525
+ /** @type {string[]} */
526
+ const allExclude = [...new Set([...existingExclude, ...ripple_package_list])];
432
527
 
433
528
  console.log(`[@ripple-ts/vite-plugin] Merged 'optimizeDeps.exclude':`, allExclude);
434
529
  console.log(
@@ -464,6 +559,8 @@ export function ripple(inlineOptions = {}) {
464
559
  loadedRippleConfig = await loadRippleConfig(root);
465
560
  }
466
561
 
562
+ if (!has_route_config(loadedRippleConfig)) return;
563
+
467
564
  renderRouteEntries = loadedRippleConfig.router.routes
468
565
  .filter((/** @type {Route} */ r) => r.type === 'render')
469
566
  .map((/** @type {RenderRoute} */ r) => r.entry);
@@ -488,6 +585,10 @@ export function ripple(inlineOptions = {}) {
488
585
  try {
489
586
  rippleConfig = await loadRippleConfig(root, { vite });
490
587
 
588
+ if (!has_route_config(rippleConfig)) {
589
+ return;
590
+ }
591
+
491
592
  // Create router from config
492
593
  router = createRouter(rippleConfig.router.routes);
493
594
  console.log(
@@ -608,18 +709,18 @@ export function ripple(inlineOptions = {}) {
608
709
  },
609
710
 
610
711
  /**
611
- * Handle HMR for .ripple files.
712
+ * Handle HMR for Ripple source files.
612
713
  *
613
714
  * Inspired by vite-plugin-svelte's approach: instead of manually
614
715
  * re-compiling in hotUpdate, we use `transformRequest` to run the
615
716
  * full Vite pipeline (load → transform). This updates cssCache
616
717
  * via the existing transform hook and avoids double-compilation.
617
718
  *
618
- * After the .ripple file is re-transformed, we invalidate and
719
+ * After the source file is re-transformed, we invalidate and
619
720
  * include the virtual CSS module in the HMR update so the browser
620
721
  * receives fresh CSS in sync with the re-rendered component.
621
722
  *
622
- * For non-.ripple files that don't self-accept, we invalidate
723
+ * For non-Ripple files that don't self-accept, we invalidate
623
724
  * SSR modules and trigger a full reload.
624
725
  */
625
726
  hotUpdate: {
@@ -629,7 +730,7 @@ export function ripple(inlineOptions = {}) {
629
730
 
630
731
  let updated_modules = modules;
631
732
 
632
- if (file.endsWith('.ripple')) {
733
+ if (is_ripple_module_path(file)) {
633
734
  const filename = file.replace(root, '');
634
735
  const cssId = createVirtualImportId(filename, root, 'style');
635
736
 
@@ -638,7 +739,7 @@ export function ripple(inlineOptions = {}) {
638
739
 
639
740
  // Use transformRequest to run the standard Vite pipeline.
640
741
  // This triggers our transform hook which re-compiles the
641
- // .ripple file and updates cssCache as a side-effect.
742
+ // source file and updates cssCache as a side-effect.
642
743
  try {
643
744
  await this.environment.transformRequest(filename);
644
745
  } catch {
@@ -659,7 +760,7 @@ export function ripple(inlineOptions = {}) {
659
760
  }
660
761
  }
661
762
 
662
- // Non-.ripple files: if all modules self-accept, let Vite
763
+ // Non-Ripple files: if all modules self-accept, let Vite
663
764
  // handle. Otherwise invalidate SSR and full-reload.
664
765
  if (modules.length > 0 && modules.every((m) => m.isSelfAccepting)) {
665
766
  return updated_modules === modules ? undefined : updated_modules;
@@ -687,7 +788,7 @@ export function ripple(inlineOptions = {}) {
687
788
  transformIndexHtml: {
688
789
  order: 'pre',
689
790
  handler(html) {
690
- if (!isBuild || isSSRBuild) return html;
791
+ if (!isBuild || isSSRBuild || !has_route_config(loadedRippleConfig)) return html;
691
792
 
692
793
  // Inject the hydration client entry script before </body>
693
794
  const hydrationScript = `<script type="module" src="virtual:ripple-hydrate"></script>`;
@@ -708,6 +809,8 @@ export function ripple(inlineOptions = {}) {
708
809
  loadedRippleConfig = await loadRippleConfig(root);
709
810
  }
710
811
 
812
+ if (!has_route_config(loadedRippleConfig)) return;
813
+
711
814
  console.log('[@ripple-ts/vite-plugin] Client build done. Starting server build...');
712
815
 
713
816
  // Re-resolve with adapter validation for production builds.
@@ -897,8 +1000,12 @@ export function ripple(inlineOptions = {}) {
897
1000
 
898
1001
  async resolveId(id, importer, options) {
899
1002
  // Handle virtual hydrate module
900
- if (id === 'virtual:ripple-hydrate') {
901
- return '\0virtual:ripple-hydrate';
1003
+ if (id === VIRTUAL_HYDRATE_ID) {
1004
+ return RESOLVED_VIRTUAL_HYDRATE_ID;
1005
+ }
1006
+
1007
+ if (id === VIRTUAL_COMPAT_ID) {
1008
+ return RESOLVED_VIRTUAL_COMPAT_ID;
902
1009
  }
903
1010
 
904
1011
  // Skip non-package imports (relative/absolute paths)
@@ -941,8 +1048,13 @@ export function ripple(inlineOptions = {}) {
941
1048
  },
942
1049
 
943
1050
  async load(id, opts) {
1051
+ if (id === RESOLVED_VIRTUAL_COMPAT_ID) {
1052
+ const compat_config = await get_current_ripple_config();
1053
+ return create_compat_virtual_module(compat_config);
1054
+ }
1055
+
944
1056
  // Handle virtual hydrate module
945
- if (id === '\0virtual:ripple-hydrate') {
1057
+ if (id === RESOLVED_VIRTUAL_HYDRATE_ID) {
946
1058
  if (isBuild && renderRouteEntries.length > 0) {
947
1059
  // Production: generate static import map so Vite bundles page components
948
1060
  const importMapLines = renderRouteEntries
@@ -955,6 +1067,7 @@ export function ripple(inlineOptions = {}) {
955
1067
  // main bundle awaits page module import → page module awaits main bundle's
956
1068
  // TLA to complete → circular wait.
957
1069
  return `
1070
+ import ${JSON.stringify(VIRTUAL_COMPAT_ID)};
958
1071
  import { hydrate, mount } from 'ripple';
959
1072
 
960
1073
  const routeModules = {
@@ -1004,6 +1117,7 @@ ${importMapLines}
1004
1117
  // Dev mode: use async IIFE to avoid top-level await deadlock
1005
1118
  // (same reason as production — page modules import from the main bundle)
1006
1119
  return `
1120
+ import ${JSON.stringify(VIRTUAL_COMPAT_ID)};
1007
1121
  import { hydrate, mount } from 'ripple';
1008
1122
 
1009
1123
  (async () => {
@@ -1045,18 +1159,23 @@ import { hydrate, mount } from 'ripple';
1045
1159
  },
1046
1160
 
1047
1161
  transform: {
1048
- filter: { id: /\.ripple$/ },
1162
+ filter: { id: RIPPLE_EXTENSION_PATTERN },
1049
1163
 
1050
1164
  async handler(code, id, opts) {
1051
1165
  const filename = id.replace(root, '');
1052
1166
  const ssr = opts?.ssr === true || this.environment.config.consumer === 'server';
1053
1167
 
1054
1168
  const is_dev = config?.command === 'serve';
1169
+ const current_ripple_config = await get_current_ripple_config();
1055
1170
 
1056
1171
  const { js, css } = await compile(code, filename, {
1057
1172
  mode: ssr ? 'server' : 'client',
1058
1173
  dev: is_dev,
1059
1174
  hmr: is_dev && !ssr,
1175
+ compat_kinds:
1176
+ current_ripple_config === null
1177
+ ? undefined
1178
+ : Object.keys(current_ripple_config.compat),
1060
1179
  });
1061
1180
 
1062
1181
  // Track modules with #server blocks for RPC (client build only)
@@ -15,12 +15,67 @@
15
15
  * and the generated production server entry.
16
16
  */
17
17
 
18
- /** @import { RippleConfigOptions, ResolvedRippleConfig } from '@ripple-ts/vite-plugin' */
18
+ /** @import { CompatFactoryConfig, RippleConfigOptions, ResolvedRippleConfig } from '@ripple-ts/vite-plugin' */
19
19
 
20
20
  import path from 'node:path';
21
21
  import fs from 'node:fs';
22
22
  import { DEFAULT_OUTDIR } from './constants.js';
23
23
 
24
+ /**
25
+ * @param {unknown} entry
26
+ * @returns {entry is CompatFactoryConfig}
27
+ */
28
+ function is_compat_descriptor(entry) {
29
+ return !!entry && typeof entry === 'object' && 'from' in entry;
30
+ }
31
+
32
+ /**
33
+ * @param {unknown} entry
34
+ * @returns {entry is { __ripple_compat__: CompatFactoryConfig }}
35
+ */
36
+ function is_compat_branded_entry(entry) {
37
+ return (
38
+ !!entry &&
39
+ (typeof entry === 'function' || typeof entry === 'object') &&
40
+ '__ripple_compat__' in entry &&
41
+ is_compat_descriptor(entry.__ripple_compat__)
42
+ );
43
+ }
44
+
45
+ /**
46
+ * @param {string} kind
47
+ * @param {unknown} entry
48
+ * @returns {CompatFactoryConfig}
49
+ */
50
+ function normalize_compat_entry(kind, entry) {
51
+ if (is_compat_branded_entry(entry)) {
52
+ entry = entry.__ripple_compat__;
53
+ }
54
+
55
+ if (!is_compat_descriptor(entry)) {
56
+ throw new Error(
57
+ `[@ripple-ts/vite-plugin] ripple.config.ts compat.${kind} must be either a compat descriptor, a compat factory, or an invoked compat entry.`,
58
+ );
59
+ }
60
+
61
+ if (typeof entry.from !== 'string' || entry.from.length === 0) {
62
+ throw new Error(
63
+ `[@ripple-ts/vite-plugin] ripple.config.ts compat.${kind}.from must be a non-empty string.`,
64
+ );
65
+ }
66
+
67
+ if (entry.factory !== undefined && typeof entry.factory !== 'string') {
68
+ throw new Error(
69
+ `[@ripple-ts/vite-plugin] ripple.config.ts compat.${kind}.factory must be a string when provided.`,
70
+ );
71
+ }
72
+
73
+ return {
74
+ from: entry.from,
75
+ ...(entry.factory ? { factory: entry.factory } : {}),
76
+ };
77
+ }
78
+
24
79
  /**
25
80
  * Validate a raw ripple config and apply all defaults.
26
81
  *
@@ -46,10 +101,6 @@ export function resolveRippleConfig(raw, options = {}) {
46
101
  );
47
102
  }
48
103
 
49
- if (!raw.router?.routes) {
50
- throw new Error('[@ripple-ts/vite-plugin] ripple.config.ts must define `router.routes`.');
51
- }
52
-
53
104
  if (requireAdapter) {
54
105
  if (!raw.adapter) {
55
106
  throw new Error(
@@ -77,9 +128,15 @@ export function resolveRippleConfig(raw, options = {}) {
77
128
  },
78
129
  adapter: raw.adapter,
79
130
  router: {
80
- routes: raw.router.routes,
131
+ routes: raw.router?.routes ?? [],
81
132
  },
82
133
  middlewares: raw.middlewares ?? [],
134
+ compat: Object.fromEntries(
135
+ Object.entries(raw.compat ?? {}).map(([kind, entry]) => [
136
+ kind,
137
+ normalize_compat_entry(kind, entry),
138
+ ]),
139
+ ),
83
140
  platform: {
84
141
  env: raw.platform?.env ?? {},
85
142
  },
package/types/index.d.ts CHANGED
@@ -99,6 +99,26 @@ declare module '@ripple-ts/vite-plugin' {
99
99
  excludeRippleExternalModules?: boolean;
100
100
  }
101
101
 
102
+ export interface CompatFactoryConfig {
103
+ /** Module specifier that exports the compat factory */
104
+ from: string;
105
+ /** Named export to call. Omit to use the module's default export. */
106
+ factory?: string;
107
+ }
108
+
109
+ export interface CompatFactory<T = unknown> {
110
+ (): T;
111
+ __ripple_compat__: CompatFactoryConfig;
112
+ }
113
+
114
+ export interface CompatEntryValue {
115
+ __ripple_compat__: CompatFactoryConfig;
116
+ }
117
+
118
+ export type CompatConfigEntry = CompatFactoryConfig | CompatFactory | CompatEntryValue;
119
+
120
+ export type CompatConfig = Record<string, CompatConfigEntry>;
121
+
102
122
  export interface RippleConfigOptions {
103
123
  build?: {
104
124
  /** Output directory for the production build. @default 'dist' */
@@ -119,11 +139,21 @@ declare module '@ripple-ts/vite-plugin' {
119
139
  */
120
140
  runtime: RuntimePrimitives;
121
141
  };
122
- router: {
142
+ router?: {
123
143
  routes: Route[];
124
144
  };
125
145
  /** Global middlewares applied to all routes */
126
146
  middlewares?: Middleware[];
147
+ /**
148
+ * Client-side TSX compat integrations keyed by kind, e.g. `react` for `<tsx:react>`.
149
+ *
150
+ * You can either pass a descriptor object or import a compat factory directly,
151
+ * as long as that factory export carries Ripple compat metadata.
152
+ *
153
+ * These are compiled into a browser-side compat registry by the Vite plugin,
154
+ * allowing `mount()` / `hydrate()` to pick them up automatically.
155
+ */
156
+ compat?: CompatConfig;
127
157
  platform?: {
128
158
  env: Record<string, string>;
129
159
  };
@@ -163,6 +193,8 @@ declare module '@ripple-ts/vite-plugin' {
163
193
  };
164
194
  /** @default [] */
165
195
  middlewares: Middleware[];
196
+ /** @default {} */
197
+ compat: Record<string, CompatFactoryConfig>;
166
198
  platform: {
167
199
  /** @default {} */
168
200
  env: Record<string, string>;