@vistagenic/vista 0.3.8 → 0.3.9
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/bin/flashpack-boot.mjs
CHANGED
|
@@ -75,20 +75,22 @@ function collect() {
|
|
|
75
75
|
const pages = [];
|
|
76
76
|
const layouts = [];
|
|
77
77
|
let root = null;
|
|
78
|
+
let rootLayout = null;
|
|
78
79
|
for (const appRel of ['app', 'src/app']) {
|
|
79
80
|
const dir = path.join(modulesDir, appRel);
|
|
80
81
|
if (!fs.existsSync(dir)) continue;
|
|
81
82
|
for (const file of walk(dir)) {
|
|
82
83
|
const relative = path.relative(dir, file).replaceAll('\\', '/');
|
|
83
84
|
const name = path.basename(relative);
|
|
84
|
-
const segments = relative.split('/').slice(0, -1);
|
|
85
|
-
const url = moduleUrl(appRel, relative);
|
|
86
|
-
if (!root && name.startsWith('root.') && segments.length === 0) root = url
|
|
87
|
-
if (
|
|
88
|
-
|
|
85
|
+
const segments = relative.split('/').slice(0, -1).filter(Boolean);
|
|
86
|
+
const url = moduleUrl(appRel, relative).split('?')[0];
|
|
87
|
+
if (!root && name.startsWith('root.') && segments.length === 0) root = url;
|
|
88
|
+
if (!rootLayout && name.startsWith('layout.') && segments.length === 0) rootLayout = url;
|
|
89
|
+
if (name.startsWith('page.') || name.startsWith('index.')) pages.push({ module: url, segments });
|
|
90
|
+
else if (name.startsWith('layout.') && segments.length > 0) layouts.push({ module: url, segments });
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
|
-
return { root: root || '/_flashpack/modules/app/root.tsx.js', pages, layouts };
|
|
93
|
+
return { root: root || rootLayout || '/_flashpack/modules/app/root.tsx.js', pages, layouts };
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
function layoutApplies(segments, parts) {
|
package/bin/flashpack-ssr.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createRequire, register } from 'node:module';
|
|
4
|
-
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
5
|
|
|
6
6
|
function readArg(name) {
|
|
7
7
|
const index = process.argv.indexOf(name);
|
|
@@ -13,6 +13,8 @@ const cwd = path.resolve(readArg('--cwd') || process.cwd());
|
|
|
13
13
|
const requestPath = readArg('--path') || '/';
|
|
14
14
|
const modulesDir = path.join(cwd, '.flash', 'dev', 'modules');
|
|
15
15
|
const shimsDir = path.join(cwd, '.flash', 'dev', 'ssr');
|
|
16
|
+
const selfDir = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const vistaPkgRoot = path.resolve(selfDir, '..');
|
|
16
18
|
|
|
17
19
|
const registered = register(new URL('./flashpack-ssr-loader.mjs', import.meta.url), {
|
|
18
20
|
parentURL: import.meta.url,
|
|
@@ -22,60 +24,179 @@ if (registered && typeof registered.then === 'function') {
|
|
|
22
24
|
await registered;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
function
|
|
26
|
-
const
|
|
27
|
-
|
|
27
|
+
function resolveFrom(roots, request) {
|
|
28
|
+
const errors = [];
|
|
29
|
+
for (const root of roots) {
|
|
30
|
+
try {
|
|
31
|
+
return createRequire(path.join(root, 'package.json'))(request);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
errors.push(`${root}: ${error && error.message ? error.message : error}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`Cannot resolve '${request}' for flashpack SSR\n${errors.join('\n')}`);
|
|
28
37
|
}
|
|
29
38
|
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
39
|
+
const react = resolveFrom([cwd, vistaPkgRoot, path.resolve(cwd, '../..'), path.resolve(cwd, '../../..')], 'react');
|
|
40
|
+
const reactDomServer = resolveFrom(
|
|
41
|
+
[cwd, vistaPkgRoot, path.resolve(cwd, '../..'), path.resolve(cwd, '../../..')],
|
|
42
|
+
'react-dom/server'
|
|
43
|
+
);
|
|
44
|
+
const { createElement } = react;
|
|
45
|
+
const { renderToReadableStream } = reactDomServer;
|
|
33
46
|
|
|
34
|
-
|
|
35
|
-
if (!
|
|
36
|
-
|
|
47
|
+
function walk(dir, found = []) {
|
|
48
|
+
if (!fs.existsSync(dir)) return found;
|
|
49
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
50
|
+
const full = path.join(dir, entry.name);
|
|
51
|
+
if (entry.isDirectory()) walk(full, found);
|
|
52
|
+
else if (entry.name.endsWith('.js')) found.push(full);
|
|
53
|
+
}
|
|
54
|
+
return found;
|
|
37
55
|
}
|
|
38
56
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
function partsOf(pathname) {
|
|
58
|
+
return String(pathname || '/')
|
|
59
|
+
.split('?')[0]
|
|
60
|
+
.split('#')[0]
|
|
61
|
+
.split('/')
|
|
62
|
+
.filter(Boolean);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function matchSegments(segments, parts) {
|
|
66
|
+
const params = {};
|
|
67
|
+
let pi = 0;
|
|
68
|
+
let staticCount = 0;
|
|
69
|
+
let dynamicCount = 0;
|
|
70
|
+
for (const seg of segments) {
|
|
71
|
+
if (seg.startsWith('(') && seg.endsWith(')')) continue;
|
|
72
|
+
if (seg.startsWith('[...') && seg.endsWith(']')) {
|
|
73
|
+
params[seg.slice(4, -1)] = parts.slice(pi);
|
|
74
|
+
dynamicCount += 1;
|
|
75
|
+
pi = parts.length;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (seg.startsWith('[') && seg.endsWith(']')) {
|
|
79
|
+
if (pi >= parts.length) return null;
|
|
80
|
+
params[seg.slice(1, -1)] = parts[pi];
|
|
81
|
+
dynamicCount += 1;
|
|
82
|
+
pi += 1;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (pi >= parts.length || parts[pi] !== seg) return null;
|
|
86
|
+
staticCount += 1;
|
|
87
|
+
pi += 1;
|
|
88
|
+
}
|
|
89
|
+
if (pi !== parts.length) return null;
|
|
90
|
+
return { params, staticCount, dynamicCount };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function layoutApplies(segments, parts) {
|
|
94
|
+
let pi = 0;
|
|
95
|
+
for (const seg of segments || []) {
|
|
96
|
+
if (seg.startsWith('(') && seg.endsWith(')')) continue;
|
|
97
|
+
if (seg.startsWith('[...')) return true;
|
|
98
|
+
if (seg.startsWith('[') && seg.endsWith(']')) {
|
|
99
|
+
if (pi >= parts.length) return false;
|
|
100
|
+
pi += 1;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (parts[pi] !== seg) return false;
|
|
104
|
+
pi += 1;
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function collectRoutes() {
|
|
110
|
+
const pages = [];
|
|
111
|
+
const layouts = [];
|
|
112
|
+
let rootFile = null;
|
|
113
|
+
let rootLayoutFile = null;
|
|
114
|
+
|
|
115
|
+
for (const appRel of ['app', 'src/app']) {
|
|
116
|
+
const dir = path.join(modulesDir, appRel);
|
|
117
|
+
if (!fs.existsSync(dir)) continue;
|
|
118
|
+
for (const file of walk(dir)) {
|
|
119
|
+
const relative = path.relative(dir, file).replaceAll('\\', '/');
|
|
120
|
+
const name = path.basename(relative);
|
|
121
|
+
const segments = relative.split('/').slice(0, -1).filter(Boolean);
|
|
122
|
+
if (!rootFile && name.startsWith('root.') && segments.length === 0) {
|
|
123
|
+
rootFile = file;
|
|
124
|
+
}
|
|
125
|
+
if (!rootLayoutFile && name.startsWith('layout.') && segments.length === 0) {
|
|
126
|
+
rootLayoutFile = file;
|
|
127
|
+
}
|
|
128
|
+
if (name.startsWith('page.') || name.startsWith('index.')) {
|
|
129
|
+
pages.push({ file, segments });
|
|
130
|
+
} else if (name.startsWith('layout.') && segments.length > 0) {
|
|
131
|
+
// Root layout is used as the document shell, not a nested layout.
|
|
132
|
+
layouts.push({ file, segments });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
rootFile: rootFile || rootLayoutFile,
|
|
139
|
+
pages,
|
|
140
|
+
layouts,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function pickPage(pages, parts) {
|
|
145
|
+
let best = null;
|
|
146
|
+
for (const page of pages) {
|
|
147
|
+
const matched = matchSegments(page.segments || [], parts);
|
|
148
|
+
if (!matched) continue;
|
|
149
|
+
const candidate = { page, ...matched };
|
|
150
|
+
if (
|
|
151
|
+
!best ||
|
|
152
|
+
candidate.staticCount > best.staticCount ||
|
|
153
|
+
(candidate.staticCount === best.staticCount && candidate.dynamicCount < best.dynamicCount)
|
|
154
|
+
) {
|
|
155
|
+
best = candidate;
|
|
156
|
+
}
|
|
51
157
|
}
|
|
158
|
+
return best;
|
|
52
159
|
}
|
|
53
160
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
161
|
+
async function renderNode(Component, props) {
|
|
162
|
+
if (
|
|
163
|
+
typeof Component === 'function' &&
|
|
164
|
+
Component.constructor &&
|
|
165
|
+
Component.constructor.name === 'AsyncFunction'
|
|
166
|
+
) {
|
|
167
|
+
return await Component(props);
|
|
168
|
+
}
|
|
169
|
+
return createElement(Component, props);
|
|
60
170
|
}
|
|
61
171
|
|
|
62
|
-
|
|
172
|
+
const table = collectRoutes();
|
|
173
|
+
if (!table.rootFile) {
|
|
174
|
+
throw new Error('flashpack ssr: app/root or app/layout was not compiled');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const pathname = requestPath.replace(/\/$/, '') || '/';
|
|
178
|
+
const parts = partsOf(pathname);
|
|
179
|
+
const match = pickPage(table.pages, parts);
|
|
180
|
+
if (!match) {
|
|
63
181
|
throw new Error(`flashpack ssr: no page module for ${pathname}`);
|
|
64
182
|
}
|
|
65
183
|
|
|
66
|
-
const rootMod = await import(pathToFileURL(rootFile).href);
|
|
67
|
-
const pageMod = await import(pathToFileURL(
|
|
184
|
+
const rootMod = await import(pathToFileURL(table.rootFile).href);
|
|
185
|
+
const pageMod = await import(pathToFileURL(match.page.file).href);
|
|
68
186
|
const Root = rootMod.default;
|
|
69
187
|
const Page = pageMod.default;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
188
|
+
|
|
189
|
+
let tree = await renderNode(Page, { params: match.params });
|
|
190
|
+
const nested = table.layouts
|
|
191
|
+
.filter((layout) => layoutApplies(layout.segments, parts))
|
|
192
|
+
.sort((a, b) => (a.segments || []).length - (b.segments || []).length);
|
|
193
|
+
for (const layout of nested) {
|
|
194
|
+
const layoutMod = await import(pathToFileURL(layout.file).href);
|
|
195
|
+
if (typeof layoutMod.default !== 'function') continue;
|
|
196
|
+
tree = await renderNode(layoutMod.default, { params: match.params, children: tree });
|
|
77
197
|
}
|
|
198
|
+
tree = createElement(Root, { children: tree });
|
|
78
199
|
|
|
79
|
-
const stream = await renderToReadableStream(
|
|
200
|
+
const stream = await renderToReadableStream(tree);
|
|
80
201
|
await stream.allReady;
|
|
81
202
|
process.stdout.write(await new Response(stream).text());
|
|
@@ -25,10 +25,10 @@ function vistaVersion() {
|
|
|
25
25
|
try {
|
|
26
26
|
const require = (0, module_1.createRequire)(__filename);
|
|
27
27
|
const pkg = require('../../package.json');
|
|
28
|
-
return pkg.version || '0.3.
|
|
28
|
+
return pkg.version || '0.3.9';
|
|
29
29
|
}
|
|
30
30
|
catch {
|
|
31
|
-
return '0.3.
|
|
31
|
+
return '0.3.9';
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
function installDevClient(cwd) {
|
|
Binary file
|