@varlabs/create-solidstep 0.1.2 → 0.1.3
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/generate/app/middleware.ts +49 -0
- package/generate/app.config.ts +26 -20
- package/generate/client.ts +26 -34
- package/generate/server.ts +336 -178
- package/generate/utils/cache.ts +106 -0
- package/generate/utils/cookies.ts +25 -0
- package/generate/utils/cors.ts +16 -0
- package/generate/utils/csp.ts +27 -0
- package/generate/utils/csrf.ts +62 -0
- package/generate/utils/redirect.ts +16 -0
- package/generate/utils/router.ts +137 -1
- package/generate/utils/server-only.ts +5 -0
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { defineMiddleware } from 'vinxi/http';
|
|
2
|
+
import { csp } from '../utils/csp';
|
|
3
|
+
import { cors } from '../utils/cors';
|
|
4
|
+
import { csrf } from '../utils/csrf';
|
|
5
|
+
|
|
6
|
+
const trustedOrigins = ['https://example.com', 'https://another-example.com'];
|
|
7
|
+
|
|
8
|
+
const corsMiddleware = cors(trustedOrigins);
|
|
9
|
+
const csrfMiddleware = csrf(trustedOrigins);
|
|
10
|
+
|
|
11
|
+
const middleware = defineMiddleware({
|
|
12
|
+
onRequest: async (event) => {
|
|
13
|
+
event.node.res.setHeader('Content-Security-Policy', csp);
|
|
14
|
+
event.node.res.setHeader('Vary', 'Origin, Access-Control-Request-Method');
|
|
15
|
+
|
|
16
|
+
const origin = event.node.req.headers.origin || '';
|
|
17
|
+
const requestUrl = new URL(event.node.req.url, `http://${event.node.req.headers.host || 'localhost'}`);
|
|
18
|
+
|
|
19
|
+
const csrfResult = csrfMiddleware(
|
|
20
|
+
event.node.req.method,
|
|
21
|
+
requestUrl,
|
|
22
|
+
origin,
|
|
23
|
+
event.node.req.headers.referer
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (!csrfResult.success) {
|
|
27
|
+
event.node.res.statusCode = 403; // Forbidden
|
|
28
|
+
event.node.res.end(csrfResult.message);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (origin) {
|
|
33
|
+
const corsHeaders = corsMiddleware(origin, event.node.req.method === 'OPTIONS');
|
|
34
|
+
for (const [key, value] of Object.entries(corsHeaders)) {
|
|
35
|
+
event.node.res.setHeader(key, value);
|
|
36
|
+
}
|
|
37
|
+
if (
|
|
38
|
+
event.node.req.method === 'OPTIONS'
|
|
39
|
+
&& event.node.req.headers['access-control-request-method']
|
|
40
|
+
) {
|
|
41
|
+
event.node.res.statusCode = 204; // No Content for preflight requests
|
|
42
|
+
event.node.res.end();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export default middleware;
|
package/generate/app.config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createApp } from 'vinxi';
|
|
2
2
|
import solid from 'vite-plugin-solid';
|
|
3
3
|
import { serverFunctions } from '@vinxi/server-functions/plugin';
|
|
4
|
-
import {
|
|
4
|
+
import { ServerRouter. ClientRouter } from './utils/router';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { dirname } from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
@@ -10,6 +10,9 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
10
10
|
|
|
11
11
|
export default createApp({
|
|
12
12
|
server: {
|
|
13
|
+
experimental: {
|
|
14
|
+
asyncContext: true,
|
|
15
|
+
},
|
|
13
16
|
},
|
|
14
17
|
routers: [
|
|
15
18
|
{
|
|
@@ -19,18 +22,14 @@ export default createApp({
|
|
|
19
22
|
base: '/',
|
|
20
23
|
},
|
|
21
24
|
{
|
|
22
|
-
name: '
|
|
23
|
-
type: '
|
|
24
|
-
|
|
25
|
-
handler: './
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
link: {
|
|
29
|
-
client: 'client',
|
|
30
|
-
},
|
|
31
|
-
middleware: './app/middleware.ts',
|
|
25
|
+
name: 'client',
|
|
26
|
+
type: 'client',
|
|
27
|
+
target: 'browser',
|
|
28
|
+
handler: './client.ts',
|
|
29
|
+
plugins: () => [serverFunctions.client(), solid({ ssr: true })],
|
|
30
|
+
base: '/_build',
|
|
32
31
|
routes: (router, app) => {
|
|
33
|
-
return new
|
|
32
|
+
return new ServerRouter(
|
|
34
33
|
{
|
|
35
34
|
dir: path.join(__dirname, 'app'),
|
|
36
35
|
extensions: ['jsx', 'js', 'tsx', 'ts'],
|
|
@@ -41,14 +40,21 @@ export default createApp({
|
|
|
41
40
|
}
|
|
42
41
|
},
|
|
43
42
|
{
|
|
44
|
-
name: '
|
|
45
|
-
type: '
|
|
46
|
-
|
|
47
|
-
handler: './
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
name: 'ssr',
|
|
44
|
+
type: 'http',
|
|
45
|
+
base: '/',
|
|
46
|
+
handler: './server.ts',
|
|
47
|
+
target: 'server',
|
|
48
|
+
plugins: () => [
|
|
49
|
+
serverFunctions.server(),
|
|
50
|
+
solid({ ssr: true })
|
|
51
|
+
],
|
|
52
|
+
// link: {
|
|
53
|
+
// client: 'client',
|
|
54
|
+
// },
|
|
55
|
+
middleware: './app/middleware.ts',
|
|
50
56
|
routes: (router, app) => {
|
|
51
|
-
return new
|
|
57
|
+
return new ClientRouter(
|
|
52
58
|
{
|
|
53
59
|
dir: path.join(__dirname, 'app'),
|
|
54
60
|
extensions: ['jsx', 'js', 'tsx', 'ts'],
|
|
@@ -58,6 +64,6 @@ export default createApp({
|
|
|
58
64
|
);
|
|
59
65
|
}
|
|
60
66
|
},
|
|
61
|
-
serverFunctions.router(),
|
|
67
|
+
// serverFunctions.router(),
|
|
62
68
|
],
|
|
63
69
|
});
|
package/generate/client.ts
CHANGED
|
@@ -8,31 +8,6 @@ const importModule = async (routeModule: any) => {
|
|
|
8
8
|
if ((import.meta as any).env.DEV) {
|
|
9
9
|
return await manifest.inputs[routeModule.src].import();
|
|
10
10
|
}
|
|
11
|
-
const assets = await manifest.inputs?.[routeModule.src].assets();
|
|
12
|
-
if (typeof window !== 'undefined' && assets && assets.length > 0) {
|
|
13
|
-
const styles = assets.filter(
|
|
14
|
-
(asset) => asset.tag === 'style' || asset.attrs.rel === 'stylesheet',
|
|
15
|
-
);
|
|
16
|
-
for (const asset of styles) {
|
|
17
|
-
const attributeString = Object.entries(asset.attrs)
|
|
18
|
-
.map(([key, value]) => `${key}="${value}"`)
|
|
19
|
-
.join(' ');
|
|
20
|
-
if (asset.tag === 'style') {
|
|
21
|
-
document.head.insertAdjacentHTML(
|
|
22
|
-
'beforeend',
|
|
23
|
-
`<style ${attributeString}>${asset.children}</style>`,
|
|
24
|
-
);
|
|
25
|
-
} else {
|
|
26
|
-
const link = document.createElement('link');
|
|
27
|
-
link.rel = 'stylesheet';
|
|
28
|
-
link.href = asset.attrs.href;
|
|
29
|
-
Object.entries(asset.attrs).forEach(([key, value]) => {
|
|
30
|
-
link.setAttribute(key, value);
|
|
31
|
-
});
|
|
32
|
-
document.head.appendChild(link);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
11
|
return await routeModule.import();
|
|
37
12
|
};
|
|
38
13
|
|
|
@@ -40,6 +15,7 @@ export const main = async (
|
|
|
40
15
|
modulePath: string,
|
|
41
16
|
routeParams: Record<string, string> = {},
|
|
42
17
|
searchParams: Record<string, string> = {},
|
|
18
|
+
loaderDataManifest: Record<string, any> = {}
|
|
43
19
|
) => {
|
|
44
20
|
// find the route that matches the path
|
|
45
21
|
const pageModule = fileRoutes.find((route) => route.path === modulePath);
|
|
@@ -47,15 +23,18 @@ export const main = async (
|
|
|
47
23
|
console.error(`No route found for path: ${modulePath}`);
|
|
48
24
|
return;
|
|
49
25
|
}
|
|
26
|
+
const pageLoaderData = loaderDataManifest[modulePath];
|
|
50
27
|
|
|
51
28
|
const segments = modulePath.split('/').slice(2);
|
|
52
29
|
if (segments.at(0)) {
|
|
53
30
|
segments.unshift('');
|
|
54
31
|
}
|
|
55
|
-
|
|
56
|
-
|
|
32
|
+
const layouts: any[] = [];
|
|
33
|
+
const layoutLoaderData: any[] = [];
|
|
34
|
+
const groups: Record<string, any> = {};
|
|
57
35
|
for (let i = 0; i < segments.length; i++) {
|
|
58
36
|
const path = '/' + segments.slice(1, segments.length - i).join('/');
|
|
37
|
+
const loaderData = loaderDataManifest[`/layout${path}`];
|
|
59
38
|
const layoutModule = fileRoutes.find((route) => {
|
|
60
39
|
const routePath = '/' + route.path.split('/').slice(2).join('/');
|
|
61
40
|
return routePath === path && (route as any).type === 'layout';
|
|
@@ -63,6 +42,9 @@ export const main = async (
|
|
|
63
42
|
if (layoutModule) {
|
|
64
43
|
layouts.unshift(layoutModule);
|
|
65
44
|
}
|
|
45
|
+
if (loaderData) {
|
|
46
|
+
layoutLoaderData.unshift(loaderData);
|
|
47
|
+
}
|
|
66
48
|
}
|
|
67
49
|
const groupModules = fileRoutes.filter((route) => {
|
|
68
50
|
const parentPath = (route as any).parent || '';
|
|
@@ -77,23 +59,32 @@ export const main = async (
|
|
|
77
59
|
const compose = layouts.reduceRight(
|
|
78
60
|
(children, layout, index) => async () => {
|
|
79
61
|
const { default: layoutModule } = await importModule(layout.$component);
|
|
80
|
-
|
|
62
|
+
const loaderData = layoutLoaderData[index] || {};
|
|
63
|
+
const slots: Record<string, any> = {};
|
|
64
|
+
const slotPromises: Promise<any>[] = [children()];
|
|
81
65
|
if (index === layouts.length - 1) {
|
|
82
66
|
// last layout, we can render slots
|
|
83
67
|
for (const [groupName, group] of Object.entries(groups)) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
68
|
+
slotPromises.push(
|
|
69
|
+
(async () => {
|
|
70
|
+
const { default: groupPage } = await importModule(group.$component);
|
|
71
|
+
const groupLoaderData = loaderDataManifest[group.path] || {};
|
|
72
|
+
slots[groupName] = () => groupPage({
|
|
73
|
+
routeParams,
|
|
74
|
+
searchParams,
|
|
75
|
+
loaderData: groupLoaderData,
|
|
76
|
+
});
|
|
77
|
+
})()
|
|
78
|
+
);
|
|
89
79
|
}
|
|
90
80
|
}
|
|
91
|
-
const childrenRendered = await
|
|
81
|
+
const [childrenRendered] = await Promise.all(slotPromises);
|
|
92
82
|
return () => layoutModule({
|
|
93
83
|
children: childrenRendered,
|
|
94
84
|
routeParams,
|
|
95
85
|
searchParams,
|
|
96
86
|
slots: slots,
|
|
87
|
+
loaderData,
|
|
97
88
|
});
|
|
98
89
|
},
|
|
99
90
|
async () => {
|
|
@@ -101,6 +92,7 @@ export const main = async (
|
|
|
101
92
|
return () => page({
|
|
102
93
|
routeParams,
|
|
103
94
|
searchParams,
|
|
95
|
+
loaderData: pageLoaderData || {},
|
|
104
96
|
});
|
|
105
97
|
}
|
|
106
98
|
);
|