@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 +21 -0
- package/package.json +3 -3
- package/src/index.js +141 -22
- package/src/load-config.js +63 -6
- package/types/index.d.ts +33 -1
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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 ===
|
|
901
|
-
return
|
|
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 ===
|
|
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:
|
|
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)
|
package/src/load-config.js
CHANGED
|
@@ -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
|
|
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>;
|