@ressjs/vite-router 0.3.0 → 0.4.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.
- package/README.md +94 -166
- package/dist/helpers/html-generator.d.ts +13 -0
- package/dist/helpers/html-generator.js +108 -0
- package/dist/helpers/request-handler.d.ts +17 -0
- package/dist/helpers/request-handler.js +79 -0
- package/dist/helpers/vite-config.d.ts +10 -0
- package/dist/helpers/vite-config.js +97 -0
- package/dist/index.d.ts +6 -9
- package/dist/index.js +21 -6
- package/dist/pages.d.ts +26 -0
- package/dist/pages.js +296 -0
- package/dist/platform.d.ts +24 -0
- package/dist/platform.js +143 -0
- package/dist/render.d.ts +13 -0
- package/dist/render.js +98 -0
- package/dist/{simple-router.d.ts → router.d.ts} +5 -21
- package/dist/router.js +118 -0
- package/package.json +19 -8
- package/dist/entry-generator.d.ts +0 -33
- package/dist/entry-generator.d.ts.map +0 -1
- package/dist/entry-generator.js +0 -112
- package/dist/entry-generator.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/page-detector.d.ts +0 -43
- package/dist/page-detector.d.ts.map +0 -1
- package/dist/page-detector.js +0 -91
- package/dist/page-detector.js.map +0 -1
- package/dist/router-factory.d.ts +0 -57
- package/dist/router-factory.d.ts.map +0 -1
- package/dist/router-factory.js +0 -152
- package/dist/router-factory.js.map +0 -1
- package/dist/simple-router.d.ts.map +0 -1
- package/dist/simple-router.js +0 -340
- package/dist/simple-router.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveEntryClient = exports.getPlatformCSSClass = exports.detectPlatform = exports.resolvePlatformCSS = exports.createRenderServer = exports.generateTempEntries = exports.getClientEntriesInput = exports.getViteInput = exports.getPages = exports.listDetectedPages = exports.createAutoViteServer = exports.createViteRouter = void 0;
|
|
4
|
+
// Main API
|
|
5
|
+
var router_1 = require("./router");
|
|
6
|
+
Object.defineProperty(exports, "createViteRouter", { enumerable: true, get: function () { return router_1.createViteRouter; } });
|
|
7
|
+
Object.defineProperty(exports, "createAutoViteServer", { enumerable: true, get: function () { return router_1.createAutoViteServer; } });
|
|
8
|
+
Object.defineProperty(exports, "listDetectedPages", { enumerable: true, get: function () { return router_1.listDetectedPages; } });
|
|
9
|
+
Object.defineProperty(exports, "getPages", { enumerable: true, get: function () { return router_1.getPages; } });
|
|
10
|
+
// Core functions for Vite config
|
|
11
|
+
var pages_1 = require("./pages");
|
|
12
|
+
Object.defineProperty(exports, "getViteInput", { enumerable: true, get: function () { return pages_1.getViteInput; } });
|
|
13
|
+
Object.defineProperty(exports, "getClientEntriesInput", { enumerable: true, get: function () { return pages_1.getClientEntriesInput; } });
|
|
14
|
+
Object.defineProperty(exports, "generateTempEntries", { enumerable: true, get: function () { return pages_1.generateTempEntries; } });
|
|
15
|
+
Object.defineProperty(exports, "createRenderServer", { enumerable: true, get: function () { return pages_1.createRenderServer; } });
|
|
16
|
+
Object.defineProperty(exports, "resolvePlatformCSS", { enumerable: true, get: function () { return pages_1.resolvePlatformCSS; } });
|
|
17
|
+
// Platform detection
|
|
18
|
+
var platform_1 = require("./platform");
|
|
19
|
+
Object.defineProperty(exports, "detectPlatform", { enumerable: true, get: function () { return platform_1.detectPlatform; } });
|
|
20
|
+
Object.defineProperty(exports, "getPlatformCSSClass", { enumerable: true, get: function () { return platform_1.getPlatformCSSClass; } });
|
|
21
|
+
Object.defineProperty(exports, "resolveEntryClient", { enumerable: true, get: function () { return platform_1.resolveEntryClient; } });
|
package/dist/pages.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PlatformInfo } from './platform';
|
|
2
|
+
export interface PageInfo {
|
|
3
|
+
route: string;
|
|
4
|
+
file: string;
|
|
5
|
+
abs: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function getPages(): PageInfo[];
|
|
8
|
+
export declare function getViteInput(): Record<string, string>;
|
|
9
|
+
export declare function loadManifest(distPath?: string): Promise<any>;
|
|
10
|
+
export declare function resolvePlatformCSS(basePath: string, platform: PlatformInfo): Promise<string>;
|
|
11
|
+
export declare function generateTempEntries(distPath?: string, platformInfo?: PlatformInfo): Promise<string[]>;
|
|
12
|
+
export declare function generateSingleEntry(page: PageInfo, entryPath: string, preferredScssFile: string): Promise<void>;
|
|
13
|
+
export declare function getClientEntriesInput(distPath?: string): Promise<Record<string, string>>;
|
|
14
|
+
export interface RenderOptions {
|
|
15
|
+
url: string;
|
|
16
|
+
props?: any;
|
|
17
|
+
}
|
|
18
|
+
export interface CreateRenderServerOptions {
|
|
19
|
+
vite?: any;
|
|
20
|
+
}
|
|
21
|
+
export declare function createRenderServer(options?: CreateRenderServerOptions): (renderOptions: RenderOptions) => Promise<{
|
|
22
|
+
html: string;
|
|
23
|
+
props: any;
|
|
24
|
+
route: string;
|
|
25
|
+
page: string;
|
|
26
|
+
}>;
|
package/dist/pages.js
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.getPages = getPages;
|
|
40
|
+
exports.getViteInput = getViteInput;
|
|
41
|
+
exports.loadManifest = loadManifest;
|
|
42
|
+
exports.resolvePlatformCSS = resolvePlatformCSS;
|
|
43
|
+
exports.generateTempEntries = generateTempEntries;
|
|
44
|
+
exports.generateSingleEntry = generateSingleEntry;
|
|
45
|
+
exports.getClientEntriesInput = getClientEntriesInput;
|
|
46
|
+
exports.createRenderServer = createRenderServer;
|
|
47
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
48
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
49
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
50
|
+
const react_1 = __importDefault(require("react"));
|
|
51
|
+
const server_1 = require("react-dom/server");
|
|
52
|
+
function getPages() {
|
|
53
|
+
// Busca tanto page.tsx como page/index.tsx y prioriza el más corto
|
|
54
|
+
const files = fast_glob_1.default.sync('app/pages/**/*.{tsx,jsx}');
|
|
55
|
+
const seen = new Set();
|
|
56
|
+
const pages = [];
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
let route = file.replace(/^app\/pages/, '').replace(/\.tsx$|\.jsx$/, '');
|
|
59
|
+
route = route.replace(/\/index$/, '') || '/';
|
|
60
|
+
// Prioriza page.tsx sobre page/index.tsx
|
|
61
|
+
if (!seen.has(route)) {
|
|
62
|
+
seen.add(route);
|
|
63
|
+
pages.push({ route, file, abs: node_path_1.default.resolve(file) });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return pages;
|
|
67
|
+
}
|
|
68
|
+
// Para Vite: input { [route]: absPath }
|
|
69
|
+
function getViteInput() {
|
|
70
|
+
const pages = getPages();
|
|
71
|
+
const input = {};
|
|
72
|
+
for (const page of pages) {
|
|
73
|
+
input[page.file.replace(/^app\/pages\//, '').replace(/\.tsx$|\.jsx$/, '')] = page.abs;
|
|
74
|
+
}
|
|
75
|
+
return input;
|
|
76
|
+
}
|
|
77
|
+
async function loadManifest(distPath = 'dist') {
|
|
78
|
+
const assetsDir = node_path_1.default.join(distPath, 'server/.vite');
|
|
79
|
+
const files = await promises_1.default.readdir(assetsDir);
|
|
80
|
+
const manifestFile = files.find(f => /^manifest-.*\.js$/.test(f));
|
|
81
|
+
if (!manifestFile)
|
|
82
|
+
throw new Error('No manifest file found in dist/server/assets');
|
|
83
|
+
// Import dinámico del manifest
|
|
84
|
+
const manifest = (await Promise.resolve(`${node_path_1.default.join(assetsDir, manifestFile)}`).then(s => __importStar(require(s)))).default;
|
|
85
|
+
return manifest;
|
|
86
|
+
}
|
|
87
|
+
async function fileExists(file) {
|
|
88
|
+
try {
|
|
89
|
+
await promises_1.default.access(file);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Platform-aware CSS resolution
|
|
98
|
+
* Busca CSS files con prioridades basadas en platform detection
|
|
99
|
+
*/
|
|
100
|
+
// Función helper para determinar si es probablemente un dispositivo móvil
|
|
101
|
+
function isProbablyMobile(platform) {
|
|
102
|
+
// Mobile si es phone/tablet, o webview (que suele ser mobile), o category native (React Native)
|
|
103
|
+
return platform.device === 'phone' ||
|
|
104
|
+
platform.device === 'tablet' ||
|
|
105
|
+
platform.category === 'webview' ||
|
|
106
|
+
platform.category === 'native';
|
|
107
|
+
}
|
|
108
|
+
async function resolvePlatformCSS(basePath, platform) {
|
|
109
|
+
// Orden de prioridad corregido: OS first → device+OS → device → mobile condicional
|
|
110
|
+
const priorities = [];
|
|
111
|
+
// 1. OS específico primero: android.scss, ios.scss
|
|
112
|
+
priorities.push(`${basePath}.${platform.os}.scss`);
|
|
113
|
+
// Para webview, agregar combinaciones específicas después del OS
|
|
114
|
+
if (platform.category === 'webview') {
|
|
115
|
+
// 2. webview.os.scss (ej: webview.android.scss, webview.ios.scss)
|
|
116
|
+
priorities.push(`${basePath}.webview.${platform.os}.scss`);
|
|
117
|
+
// 3. webview.scss general
|
|
118
|
+
priorities.push(`${basePath}.webview.scss`);
|
|
119
|
+
}
|
|
120
|
+
// Para native o web, agregar combinaciones device.os
|
|
121
|
+
if (platform.category === 'native' || platform.category === 'web') {
|
|
122
|
+
// 2. device.os.scss (ej: phone.android.scss, tablet.ios.scss)
|
|
123
|
+
priorities.push(`${basePath}.${platform.device}.${platform.os}.scss`);
|
|
124
|
+
}
|
|
125
|
+
// 3. Device específico: phone.scss, tablet.scss, desktop.scss
|
|
126
|
+
priorities.push(`${basePath}.${platform.device}.scss`);
|
|
127
|
+
// 4. Mobile fallback SOLO si es probablemente móvil
|
|
128
|
+
if (isProbablyMobile(platform)) {
|
|
129
|
+
priorities.push(`${basePath}.mobile.scss`);
|
|
130
|
+
}
|
|
131
|
+
// 5. Fallback final
|
|
132
|
+
priorities.push(`${basePath}.scss`);
|
|
133
|
+
// Retorna el primer archivo que exista
|
|
134
|
+
for (const file of priorities) {
|
|
135
|
+
if (await fileExists(file)) {
|
|
136
|
+
return file;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Si ninguno existe, retorna el CSS base
|
|
140
|
+
return `${basePath}.scss`;
|
|
141
|
+
}
|
|
142
|
+
async function generateTempEntries(distPath = 'dist', platformInfo) {
|
|
143
|
+
const pages = getPages();
|
|
144
|
+
const entriesDir = node_path_1.default.resolve(distPath, '.entries');
|
|
145
|
+
// Solo crea el directorio si no existe, no lo borra
|
|
146
|
+
await promises_1.default.mkdir(entriesDir, { recursive: true });
|
|
147
|
+
const promises = pages.map(async (page) => {
|
|
148
|
+
const rel = page.file.replace(/^app\/pages\//, '').replace(/\.tsx$|\.jsx$/, '').replace(/\//g, '__');
|
|
149
|
+
if (platformInfo && platformInfo.device !== 'desktop') {
|
|
150
|
+
// Solo genera entry específico para mobile devices (phone, tablet)
|
|
151
|
+
const basePath = page.file.replace(/\.(tsx|jsx)$/, '');
|
|
152
|
+
const platformCSS = await resolvePlatformCSS(basePath, platformInfo);
|
|
153
|
+
// Generar entry específico con el CSS resuelto (puede ser base o específico)
|
|
154
|
+
const platformSuffix = `${platformInfo.device}.${platformInfo.os}`;
|
|
155
|
+
const entryPath = node_path_1.default.join(entriesDir, rel + `__entry-client.${platformSuffix}.tsx`);
|
|
156
|
+
await generateSingleEntry(page, entryPath, platformCSS);
|
|
157
|
+
return [entryPath];
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// Lógica mejorada: busca dinámicamente todos los archivos CSS específicos
|
|
161
|
+
const basePath = page.file.replace(/\.(tsx|jsx)$/, '');
|
|
162
|
+
const baseScss = basePath + '.scss';
|
|
163
|
+
const results = [];
|
|
164
|
+
// Genera entry-client base (desktop) - solo CSS base
|
|
165
|
+
const entryPath = node_path_1.default.join(entriesDir, rel + '__entry-client.tsx');
|
|
166
|
+
await generateSingleEntry(page, entryPath, baseScss);
|
|
167
|
+
results.push(entryPath);
|
|
168
|
+
// Buscar dinámicamente todos los archivos CSS específicos de plataforma
|
|
169
|
+
const glob = await Promise.resolve().then(() => __importStar(require('fast-glob')));
|
|
170
|
+
const cssFiles = await glob.default(`${basePath}.*.scss`);
|
|
171
|
+
for (const cssFile of cssFiles) {
|
|
172
|
+
// Extraer el sufijo del archivo CSS (ej: mobile, webview.android, desktop.linux)
|
|
173
|
+
const suffix = cssFile
|
|
174
|
+
.replace(basePath + '.', '')
|
|
175
|
+
.replace('.scss', '');
|
|
176
|
+
// Generar entry-client específico
|
|
177
|
+
const specificEntryPath = node_path_1.default.join(entriesDir, rel + `__entry-client.${suffix}.tsx`);
|
|
178
|
+
await generateSingleEntry(page, specificEntryPath, cssFile);
|
|
179
|
+
results.push(specificEntryPath);
|
|
180
|
+
}
|
|
181
|
+
return results;
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
const entryFiles = await Promise.all(promises);
|
|
185
|
+
return entryFiles.flat().filter(Boolean);
|
|
186
|
+
}
|
|
187
|
+
async function generateSingleEntry(page, entryPath, preferredScssFile) {
|
|
188
|
+
// Verifica si el archivo ya existe y es más reciente que el archivo fuente
|
|
189
|
+
try {
|
|
190
|
+
const entryStat = await promises_1.default.stat(entryPath);
|
|
191
|
+
const pageStat = await promises_1.default.stat(page.abs);
|
|
192
|
+
if (entryStat.mtime > pageStat.mtime) {
|
|
193
|
+
return; // El entry ya existe y está actualizado
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// El archivo no existe, continúa con la generación
|
|
198
|
+
}
|
|
199
|
+
const importPath = node_path_1.default.relative(node_path_1.default.dirname(entryPath), page.abs).replace(/\\/g, '/');
|
|
200
|
+
// Lógica de fallback: intenta cargar el CSS preferido, si no existe carga el base
|
|
201
|
+
let scssToImport = '';
|
|
202
|
+
let scssImport = '';
|
|
203
|
+
if (await fileExists(preferredScssFile)) {
|
|
204
|
+
// Existe el CSS específico, usarlo
|
|
205
|
+
scssToImport = preferredScssFile;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
// No existe el CSS específico, usar el CSS base como fallback
|
|
209
|
+
const baseScss = page.file.replace(/\.(tsx|jsx)$/, '.scss');
|
|
210
|
+
if (await fileExists(baseScss)) {
|
|
211
|
+
scssToImport = baseScss;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (scssToImport) {
|
|
215
|
+
const scssImportPath = node_path_1.default.relative(node_path_1.default.dirname(entryPath), scssToImport).replace(/\\/g, '/');
|
|
216
|
+
scssImport = `import "${scssImportPath.startsWith('.') ? scssImportPath : './' + scssImportPath}";`;
|
|
217
|
+
}
|
|
218
|
+
const template = `
|
|
219
|
+
${scssImport}
|
|
220
|
+
import { StrictMode } from "react";
|
|
221
|
+
import { hydrateRoot } from "react-dom/client";
|
|
222
|
+
import Page from "${importPath.startsWith('.') ? importPath : './' + importPath}";
|
|
223
|
+
const props = window.__INITIAL_PROPS__ || {};
|
|
224
|
+
hydrateRoot(document.getElementById("root"), <StrictMode><Page {...props} /></StrictMode>);
|
|
225
|
+
`.trim();
|
|
226
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(entryPath), { recursive: true });
|
|
227
|
+
await promises_1.default.writeFile(entryPath, template);
|
|
228
|
+
}
|
|
229
|
+
async function getClientEntriesInput(distPath = 'dist') {
|
|
230
|
+
await generateTempEntries(distPath);
|
|
231
|
+
// Incluir todos los entry-clients: base, mobile, desktop.linux
|
|
232
|
+
const entryFiles = fast_glob_1.default.sync(`${distPath}/.entries/**/*__entry-client*.tsx`);
|
|
233
|
+
const input = {};
|
|
234
|
+
for (const file of entryFiles) {
|
|
235
|
+
// Mantener el nombre completo del archivo como key para diferenciar las plataformas
|
|
236
|
+
const key = file.replace(new RegExp(`^${distPath}/\\.entries/`), '');
|
|
237
|
+
input[key] = node_path_1.default.resolve(file);
|
|
238
|
+
}
|
|
239
|
+
return input;
|
|
240
|
+
}
|
|
241
|
+
function createRenderServer(options = {}) {
|
|
242
|
+
const pages = getPages();
|
|
243
|
+
const { vite } = options;
|
|
244
|
+
return async (renderOptions) => {
|
|
245
|
+
const { url, props = {} } = renderOptions;
|
|
246
|
+
// Encuentra la página que coincide con la URL
|
|
247
|
+
const matchedPage = pages.find(page => {
|
|
248
|
+
if (page.route === '/')
|
|
249
|
+
return url === '/' || url === '';
|
|
250
|
+
return url === page.route || url.startsWith(page.route + '/');
|
|
251
|
+
});
|
|
252
|
+
if (!matchedPage) {
|
|
253
|
+
throw new Error(`Page not found for URL: ${url}`);
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
// Import dinámico de la página usando vite.ssrLoadModule si está disponible
|
|
257
|
+
let pageModule;
|
|
258
|
+
if (vite) {
|
|
259
|
+
// En desarrollo: usar vite.ssrLoadModule para manejar .tsx
|
|
260
|
+
const modulePath = `/${matchedPage.file}`;
|
|
261
|
+
pageModule = await vite.ssrLoadModule(modulePath);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// En producción: usar import directo del archivo compilado
|
|
265
|
+
const moduleUrl = matchedPage.abs.replace(/\.tsx$/, '.js').replace('/app/pages/', '/dist/server/');
|
|
266
|
+
pageModule = await Promise.resolve(`${moduleUrl}`).then(s => __importStar(require(s)));
|
|
267
|
+
}
|
|
268
|
+
const PageComponent = pageModule.default;
|
|
269
|
+
const getServerSideProps = pageModule.getServerSideProps;
|
|
270
|
+
if (!PageComponent) {
|
|
271
|
+
throw new Error(`No default export found in ${matchedPage.file}`);
|
|
272
|
+
}
|
|
273
|
+
// Si existe getServerSideProps, la ejecutamos para obtener props iniciales
|
|
274
|
+
let finalProps = { ...props };
|
|
275
|
+
if (getServerSideProps && typeof getServerSideProps === 'function') {
|
|
276
|
+
console.log(`Calling getServerSideProps for ${matchedPage.route}`);
|
|
277
|
+
const serverSideResult = await getServerSideProps();
|
|
278
|
+
if (serverSideResult && serverSideResult.props) {
|
|
279
|
+
finalProps = { ...finalProps, ...serverSideResult.props };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// Renderiza el componente con las props finales
|
|
283
|
+
const html = (0, server_1.renderToString)(react_1.default.createElement(PageComponent, finalProps));
|
|
284
|
+
return {
|
|
285
|
+
html,
|
|
286
|
+
props: finalProps,
|
|
287
|
+
route: matchedPage.route,
|
|
288
|
+
page: matchedPage.file
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
console.error(`Error rendering page ${matchedPage.route}:`, error);
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform detection for ress.js
|
|
3
|
+
* Combina headers custom de React Native con User-Agent parsing
|
|
4
|
+
*/
|
|
5
|
+
export interface PlatformInfo {
|
|
6
|
+
category: 'web' | 'webview' | 'native';
|
|
7
|
+
os: 'ios' | 'android' | 'windows' | 'macos' | 'linux' | 'unknown';
|
|
8
|
+
device: 'phone' | 'tablet' | 'desktop';
|
|
9
|
+
version?: string;
|
|
10
|
+
appVersion?: string;
|
|
11
|
+
source: 'headers' | 'user-agent';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Detecta plataforma usando headers custom + User-Agent fallback
|
|
15
|
+
*/
|
|
16
|
+
export declare function detectPlatform(userAgent: string, headers?: any): PlatformInfo;
|
|
17
|
+
/**
|
|
18
|
+
* Genera CSS class para HTML basado en platform info
|
|
19
|
+
*/
|
|
20
|
+
export declare function getPlatformCSSClass(platform: PlatformInfo): string;
|
|
21
|
+
/**
|
|
22
|
+
* Determina qué entry-client usar basado en platform
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveEntryClient(baseName: string, platform: PlatformInfo): string;
|
package/dist/platform.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Platform detection for ress.js
|
|
4
|
+
* Combina headers custom de React Native con User-Agent parsing
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.detectPlatform = detectPlatform;
|
|
8
|
+
exports.getPlatformCSSClass = getPlatformCSSClass;
|
|
9
|
+
exports.resolveEntryClient = resolveEntryClient;
|
|
10
|
+
/**
|
|
11
|
+
* Detecta plataforma usando headers custom + User-Agent fallback
|
|
12
|
+
*/
|
|
13
|
+
function detectPlatform(userAgent, headers = {}) {
|
|
14
|
+
const ressHeaders = headers;
|
|
15
|
+
// 1. React Native headers detection
|
|
16
|
+
if (ressHeaders['x-rn-platform']) {
|
|
17
|
+
return {
|
|
18
|
+
category: 'native',
|
|
19
|
+
os: ressHeaders['x-rn-platform'] || 'android',
|
|
20
|
+
device: 'phone', // React Native es típicamente mobile
|
|
21
|
+
version: ressHeaders['x-rn-version'],
|
|
22
|
+
source: 'headers'
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
// 2. WebView headers detection
|
|
26
|
+
if (ressHeaders['x-webview'] === 'true' || ressHeaders['x-ressjs-platform'] === 'webview') {
|
|
27
|
+
const os = ressHeaders['x-platform'] || ressHeaders['x-ressjs-os'] || 'android';
|
|
28
|
+
return {
|
|
29
|
+
category: 'webview',
|
|
30
|
+
os: os,
|
|
31
|
+
device: ressHeaders['x-ressjs-device'] || 'phone',
|
|
32
|
+
version: ressHeaders['x-ressjs-version'],
|
|
33
|
+
appVersion: ressHeaders['x-ressjs-app-version'],
|
|
34
|
+
source: 'headers'
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// 3. Ress.js specific headers (original logic)
|
|
38
|
+
if (ressHeaders['x-ressjs-platform'] === 'webview') {
|
|
39
|
+
return {
|
|
40
|
+
category: 'webview',
|
|
41
|
+
os: ressHeaders['x-ressjs-os'] || 'android',
|
|
42
|
+
device: ressHeaders['x-ressjs-device'] || 'phone',
|
|
43
|
+
version: ressHeaders['x-ressjs-version'],
|
|
44
|
+
appVersion: ressHeaders['x-ressjs-app-version'],
|
|
45
|
+
source: 'headers'
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// 4. User-Agent fallback detection
|
|
49
|
+
return detectFromUserAgent(userAgent);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Detección por User-Agent (navegadores estándar)
|
|
53
|
+
*/
|
|
54
|
+
function detectFromUserAgent(userAgent) {
|
|
55
|
+
const ua = userAgent.toLowerCase();
|
|
56
|
+
// SPECIAL CASE: React Native
|
|
57
|
+
if (ua.includes('reactnative')) {
|
|
58
|
+
let os = 'android';
|
|
59
|
+
if (ua.includes('ios')) {
|
|
60
|
+
os = 'ios';
|
|
61
|
+
}
|
|
62
|
+
else if (ua.includes('android')) {
|
|
63
|
+
os = 'android';
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
category: 'native',
|
|
67
|
+
os,
|
|
68
|
+
device: 'phone', // React Native siempre mobile
|
|
69
|
+
source: 'user-agent'
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// Detectar OS
|
|
73
|
+
let os = 'unknown';
|
|
74
|
+
if (ua.includes('iphone') || ua.includes('ipad') || ua.includes('ios')) {
|
|
75
|
+
os = 'ios';
|
|
76
|
+
}
|
|
77
|
+
else if (ua.includes('android')) {
|
|
78
|
+
os = 'android';
|
|
79
|
+
}
|
|
80
|
+
else if (ua.includes('windows')) {
|
|
81
|
+
os = 'windows';
|
|
82
|
+
}
|
|
83
|
+
else if (ua.includes('macintosh') || ua.includes('mac os x')) {
|
|
84
|
+
os = 'macos';
|
|
85
|
+
}
|
|
86
|
+
else if (ua.includes('linux')) {
|
|
87
|
+
os = 'linux';
|
|
88
|
+
}
|
|
89
|
+
// Detectar device type
|
|
90
|
+
let device = 'desktop';
|
|
91
|
+
if (ua.includes('ipad')) {
|
|
92
|
+
device = 'tablet';
|
|
93
|
+
}
|
|
94
|
+
else if (ua.includes('mobile') || ua.includes('phone')) {
|
|
95
|
+
device = 'phone';
|
|
96
|
+
}
|
|
97
|
+
else if (ua.includes('tablet')) {
|
|
98
|
+
device = 'tablet';
|
|
99
|
+
}
|
|
100
|
+
// Detectar category (web vs webview)
|
|
101
|
+
let category = 'web';
|
|
102
|
+
return {
|
|
103
|
+
category,
|
|
104
|
+
os,
|
|
105
|
+
device,
|
|
106
|
+
source: 'user-agent'
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Genera CSS class para HTML basado en platform info
|
|
111
|
+
*/
|
|
112
|
+
function getPlatformCSSClass(platform) {
|
|
113
|
+
const classes = [
|
|
114
|
+
platform.category, // 'web', 'webview', 'native'
|
|
115
|
+
platform.device, // 'phone', 'tablet', 'desktop'
|
|
116
|
+
platform.os // 'ios', 'android', etc.
|
|
117
|
+
];
|
|
118
|
+
return classes.join(' ');
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Determina qué entry-client usar basado en platform
|
|
122
|
+
*/
|
|
123
|
+
function resolveEntryClient(baseName, platform) {
|
|
124
|
+
const candidates = [
|
|
125
|
+
// Específico para mobile: device.os (phone.ios, tablet.android)
|
|
126
|
+
...(platform.device !== 'desktop'
|
|
127
|
+
? [`${baseName}__entry-client.${platform.device}.${platform.os}.tsx`]
|
|
128
|
+
: []),
|
|
129
|
+
// OS específico: ios, android
|
|
130
|
+
`${baseName}__entry-client.${platform.os}.tsx`,
|
|
131
|
+
// Device: mobile, desktop, tablet
|
|
132
|
+
`${baseName}__entry-client.${platform.device}.tsx`,
|
|
133
|
+
// Legacy compatibility
|
|
134
|
+
...(platform.device === 'phone' || platform.device === 'tablet'
|
|
135
|
+
? [`${baseName}__entry-client.mobile.tsx`]
|
|
136
|
+
: []),
|
|
137
|
+
// Fallback
|
|
138
|
+
`${baseName}__entry-client.tsx`
|
|
139
|
+
];
|
|
140
|
+
// En producción, deberías verificar que el archivo existe
|
|
141
|
+
// Por ahora retorna el primer candidato
|
|
142
|
+
return candidates[0];
|
|
143
|
+
}
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface RenderOptions {
|
|
2
|
+
url: string;
|
|
3
|
+
props?: any;
|
|
4
|
+
}
|
|
5
|
+
export interface CreateRenderServerOptions {
|
|
6
|
+
vite?: any;
|
|
7
|
+
}
|
|
8
|
+
export declare function createRenderServer(options?: CreateRenderServerOptions): (renderOptions: RenderOptions) => Promise<{
|
|
9
|
+
html: string;
|
|
10
|
+
props: any;
|
|
11
|
+
route: string;
|
|
12
|
+
page: string;
|
|
13
|
+
}>;
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.createRenderServer = createRenderServer;
|
|
40
|
+
const react_1 = __importDefault(require("react"));
|
|
41
|
+
const server_1 = require("react-dom/server");
|
|
42
|
+
const pages_1 = require("./pages");
|
|
43
|
+
function createRenderServer(options = {}) {
|
|
44
|
+
const pages = (0, pages_1.getPages)();
|
|
45
|
+
const { vite } = options;
|
|
46
|
+
return async (renderOptions) => {
|
|
47
|
+
const { url, props = {} } = renderOptions;
|
|
48
|
+
// Encuentra la página que coincide con la URL
|
|
49
|
+
const matchedPage = pages.find(page => {
|
|
50
|
+
if (page.route === '/')
|
|
51
|
+
return url === '/' || url === '';
|
|
52
|
+
return url === page.route || url.startsWith(page.route + '/');
|
|
53
|
+
});
|
|
54
|
+
if (!matchedPage) {
|
|
55
|
+
throw new Error(`Page not found for URL: ${url}`);
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
// Import dinámico de la página usando vite.ssrLoadModule si está disponible
|
|
59
|
+
let pageModule;
|
|
60
|
+
if (vite) {
|
|
61
|
+
// En desarrollo: usar vite.ssrLoadModule para manejar .tsx
|
|
62
|
+
const modulePath = `/${matchedPage.file}`;
|
|
63
|
+
pageModule = await vite.ssrLoadModule(modulePath);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// En producción: usar import directo del archivo compilado
|
|
67
|
+
const moduleUrl = matchedPage.abs.replace(/\.tsx$/, '.js').replace('/app/pages/', '/dist/server/');
|
|
68
|
+
pageModule = await Promise.resolve(`${moduleUrl}`).then(s => __importStar(require(s)));
|
|
69
|
+
}
|
|
70
|
+
const PageComponent = pageModule.default;
|
|
71
|
+
const getServerSideProps = pageModule.getServerSideProps;
|
|
72
|
+
if (!PageComponent) {
|
|
73
|
+
throw new Error(`No default export found in ${matchedPage.file}`);
|
|
74
|
+
}
|
|
75
|
+
// Si existe getServerSideProps, la ejecutamos para obtener props iniciales
|
|
76
|
+
let finalProps = { ...props };
|
|
77
|
+
if (getServerSideProps && typeof getServerSideProps === 'function') {
|
|
78
|
+
console.log(`Calling getServerSideProps for ${matchedPage.route}`);
|
|
79
|
+
const serverSideResult = await getServerSideProps();
|
|
80
|
+
if (serverSideResult && serverSideResult.props) {
|
|
81
|
+
finalProps = { ...finalProps, ...serverSideResult.props };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Renderiza el componente con las props finales
|
|
85
|
+
const html = (0, server_1.renderToString)(react_1.default.createElement(PageComponent, finalProps));
|
|
86
|
+
return {
|
|
87
|
+
html,
|
|
88
|
+
props: finalProps,
|
|
89
|
+
route: matchedPage.route,
|
|
90
|
+
page: matchedPage.file
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
console.error(`Error rendering page ${matchedPage.route}:`, error);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
export interface ViteRouterOptions {
|
|
3
3
|
enablePlatformDetection?: boolean;
|
|
4
|
+
isProduction?: boolean;
|
|
5
|
+
pagesDirectory?: string;
|
|
6
|
+
basePath?: string;
|
|
4
7
|
vite?: any;
|
|
5
|
-
manifest?: any;
|
|
6
8
|
templateHtml?: string;
|
|
7
|
-
isProduction?: boolean;
|
|
8
9
|
renderFunction?: (options: {
|
|
9
10
|
url: string;
|
|
10
11
|
platform: string;
|
|
@@ -16,28 +17,11 @@ export interface AutoViteServerOptions {
|
|
|
16
17
|
base?: string;
|
|
17
18
|
enablePlatformDetection?: boolean;
|
|
18
19
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Crea un servidor Express completo con vite-router automático
|
|
21
|
-
* Esta es la función de alto nivel para casos simples
|
|
22
|
-
*/
|
|
23
20
|
export declare function createAutoViteServer(options?: AutoViteServerOptions): Promise<{
|
|
24
21
|
app: import("express-serve-static-core").Express;
|
|
25
22
|
vite: any;
|
|
26
23
|
start: () => import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>;
|
|
27
24
|
}>;
|
|
28
|
-
/**
|
|
29
|
-
* Crea un Express router que automáticamente detecta páginas, las rutea,
|
|
30
|
-
* y maneja todo el SSR + hidratación del cliente.
|
|
31
|
-
*
|
|
32
|
-
* Si no se pasan opciones, auto-configura Vite, manifest, templates, etc.
|
|
33
|
-
*/
|
|
34
25
|
export declare function createViteRouter(options?: ViteRouterOptions): Router;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
*/
|
|
38
|
-
export declare function detectPlatform(userAgent: string): 'desktop' | 'mobile';
|
|
39
|
-
/**
|
|
40
|
-
* Helper para listar páginas detectadas
|
|
41
|
-
*/
|
|
42
|
-
export declare function listDetectedPages(): import("@ressjs/core").PageInfo[];
|
|
43
|
-
export { getPages } from '@ressjs/core';
|
|
26
|
+
export declare function listDetectedPages(): import("./pages").PageInfo[];
|
|
27
|
+
export { getPages } from './pages';
|