vike-lite 1.0.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 +60 -0
- package/dist/matchRoute-BONrLRYy.mjs +24 -0
- package/dist/server.d.mts +16 -0
- package/dist/server.mjs +172 -0
- package/dist/shared.d.mts +7 -0
- package/dist/shared.mjs +2 -0
- package/dist/virtuals-CR__ZAgP.mjs +8 -0
- package/dist/vite.d.mts +14 -0
- package/dist/vite.mjs +245 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# vike-lite
|
|
2
|
+
|
|
3
|
+
### ⬇️ Clone
|
|
4
|
+
```sh
|
|
5
|
+
git clone https://github.com/node-ecosystem/vike-lite.git
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
### ⚙️ Install
|
|
9
|
+
| Package Manager | Command
|
|
10
|
+
| - | -
|
|
11
|
+
| **npm** | `npm install -D vike-lite`
|
|
12
|
+
| **yarn** | `yarn add -D vike-lite`
|
|
13
|
+
| **pnpm** | `pnpm add -D vike-lite`
|
|
14
|
+
|
|
15
|
+
### 📖 Usage
|
|
16
|
+
Add Vite plugin
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
// vite.config.ts
|
|
20
|
+
import vikeLite from 'vike-lite/vite'
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
plugins: [
|
|
24
|
+
vikeLite()
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
#### Hooks
|
|
30
|
+
|
|
31
|
+
### renderPage
|
|
32
|
+
```ts
|
|
33
|
+
// /server/index.ts
|
|
34
|
+
import { cors } from 'hono/cors'
|
|
35
|
+
import { Hono } from 'hono'
|
|
36
|
+
import { renderPage } from 'vike-lite/server'
|
|
37
|
+
|
|
38
|
+
import apiRoutes from './apiRoutes'
|
|
39
|
+
|
|
40
|
+
const app = new Hono()
|
|
41
|
+
|
|
42
|
+
if (process.env.NODE_ENV === 'production') {
|
|
43
|
+
app.use(cors())
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
app.route('/api', apiRoutes)
|
|
47
|
+
|
|
48
|
+
// Catch-all remaining requests using custom rendering
|
|
49
|
+
app.get('*', async (c, next) => {
|
|
50
|
+
const response = await renderPage(c.req.raw)
|
|
51
|
+
return response ?? next()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
app.onError((error, c) => {
|
|
55
|
+
console.error(error)
|
|
56
|
+
return c.json({ error: 'Internal Server Error' }, 500)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
export default app
|
|
60
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/shared/matchRoute.ts
|
|
2
|
+
function matchRoute(urlPathname, routes) {
|
|
3
|
+
for (const route of routes) {
|
|
4
|
+
const paramNames = [];
|
|
5
|
+
const regexPath = route.path.replaceAll(/:([^/]+)/g, (_, paramName) => {
|
|
6
|
+
paramNames.push(paramName);
|
|
7
|
+
return "([^/]+)";
|
|
8
|
+
});
|
|
9
|
+
const regex = new RegExp(`^${regexPath}/?$`);
|
|
10
|
+
const match = urlPathname.match(regex);
|
|
11
|
+
if (match) {
|
|
12
|
+
let index = 0;
|
|
13
|
+
const routeParams = {};
|
|
14
|
+
for (const name of paramNames) routeParams[name] = match[++index];
|
|
15
|
+
return {
|
|
16
|
+
route,
|
|
17
|
+
routeParams
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { matchRoute as t };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/server/store.d.ts
|
|
2
|
+
interface VikeState {
|
|
3
|
+
routes: any[];
|
|
4
|
+
errorRoute: any | null;
|
|
5
|
+
config: {
|
|
6
|
+
onRenderHtml: () => Promise<any>;
|
|
7
|
+
} | null;
|
|
8
|
+
manifest: Record<string, any> | undefined;
|
|
9
|
+
}
|
|
10
|
+
declare const store: VikeState;
|
|
11
|
+
declare function setVikeState(newState: Partial<VikeState>): void;
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/server/renderPage.d.ts
|
|
14
|
+
declare function renderPage(req: Request): Promise<Response>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { VikeState, renderPage, setVikeState, store };
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { n as virtualEntryClientId } from "./virtuals-CR__ZAgP.mjs";
|
|
2
|
+
import { t as matchRoute } from "./matchRoute-BONrLRYy.mjs";
|
|
3
|
+
//#region src/server/store.ts
|
|
4
|
+
const store = {
|
|
5
|
+
routes: [],
|
|
6
|
+
errorRoute: null,
|
|
7
|
+
config: null,
|
|
8
|
+
manifest: void 0
|
|
9
|
+
};
|
|
10
|
+
function setVikeState(newState) {
|
|
11
|
+
Object.assign(store, newState);
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/server/abort.ts
|
|
15
|
+
var AbortRender = class extends Error {
|
|
16
|
+
statusCode;
|
|
17
|
+
reason;
|
|
18
|
+
constructor(statusCode, reason) {
|
|
19
|
+
super(`Render aborted with status ${statusCode}: ${reason || ""}`);
|
|
20
|
+
this.statusCode = statusCode;
|
|
21
|
+
this.reason = reason;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/server/renderPage.tsx
|
|
26
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
27
|
+
const ESCAPE_LOOKUP = {
|
|
28
|
+
"&": String.raw`\u0026`,
|
|
29
|
+
">": String.raw`\u003e`,
|
|
30
|
+
"<": String.raw`\u003c`,
|
|
31
|
+
"\u2028": String.raw`\u2028`,
|
|
32
|
+
"\u2029": String.raw`\u2029`
|
|
33
|
+
};
|
|
34
|
+
const ESCAPE_REGEX = /[&><\u{2028}\u{2029}]/gu;
|
|
35
|
+
function serializeContext(data) {
|
|
36
|
+
return JSON.stringify(data).replaceAll(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
|
|
37
|
+
}
|
|
38
|
+
function getAssets(pageModuleId) {
|
|
39
|
+
if (!isProd) return {
|
|
40
|
+
cssLinks: "",
|
|
41
|
+
jsPreloads: "",
|
|
42
|
+
entryClient: "/@id/virtual:entry-client"
|
|
43
|
+
};
|
|
44
|
+
const cssFiles = /* @__PURE__ */ new Set();
|
|
45
|
+
const jsFiles = /* @__PURE__ */ new Set();
|
|
46
|
+
const manifest = store.manifest;
|
|
47
|
+
function collectAssets(key) {
|
|
48
|
+
const chunk = manifest[key];
|
|
49
|
+
if (!chunk) throw new Error(`Asset not found in manifest for key: ${key}`);
|
|
50
|
+
jsFiles.add(chunk.file);
|
|
51
|
+
if (chunk.css) for (const css of chunk.css) cssFiles.add(css);
|
|
52
|
+
if (chunk.imports) for (const imp of chunk.imports) collectAssets(imp);
|
|
53
|
+
}
|
|
54
|
+
collectAssets(virtualEntryClientId);
|
|
55
|
+
collectAssets(pageModuleId.replace("@/", ""));
|
|
56
|
+
return {
|
|
57
|
+
cssLinks: [...cssFiles].map((href) => `<link rel="stylesheet" href="/${href}">`).join(""),
|
|
58
|
+
jsPreloads: [...jsFiles].map((href) => `<link rel="modulepreload" href="/${href}">`).join(""),
|
|
59
|
+
entryClient: "/" + manifest[virtualEntryClientId].file
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function buildPageContext(pathname, fullUrl, isJsonRequest) {
|
|
63
|
+
const matched = matchRoute(pathname, store.routes);
|
|
64
|
+
if (!matched) return null;
|
|
65
|
+
const { route, routeParams } = matched;
|
|
66
|
+
const pageContext = {
|
|
67
|
+
routeParams,
|
|
68
|
+
urlOriginal: fullUrl,
|
|
69
|
+
urlPathname: pathname
|
|
70
|
+
};
|
|
71
|
+
const [dataMod, titleMod, PageModule, HeadModule, LayoutModule] = await Promise.all([
|
|
72
|
+
route.data?.() ?? null,
|
|
73
|
+
route.title?.() ?? null,
|
|
74
|
+
isJsonRequest ? null : route.Page(),
|
|
75
|
+
isJsonRequest ? null : route.Head?.() ?? null,
|
|
76
|
+
isJsonRequest ? null : route.Layout?.() ?? null
|
|
77
|
+
]);
|
|
78
|
+
if (dataMod) try {
|
|
79
|
+
pageContext.data = await (dataMod.data ?? dataMod.default)(pageContext);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error("+data hook failed at:", pathname);
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
if (titleMod) try {
|
|
85
|
+
const titleFn = titleMod.title ?? titleMod.default;
|
|
86
|
+
pageContext.title = typeof titleFn === "function" ? titleFn(pageContext) : titleFn;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error("+title hook failed at:", pathname);
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
pageContext,
|
|
93
|
+
route,
|
|
94
|
+
PageModule,
|
|
95
|
+
HeadModule,
|
|
96
|
+
LayoutModule
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async function renderErrorPage(req, status, originalPathname, error) {
|
|
100
|
+
if (!store.errorRoute) return new Response(status === 404 ? "Not Found" : "Internal Server Error", { status });
|
|
101
|
+
try {
|
|
102
|
+
const { onRenderHtml } = await store.config.onRenderHtml();
|
|
103
|
+
const [PageModule, HeadModule, LayoutModule] = await Promise.all([
|
|
104
|
+
store.errorRoute.Page(),
|
|
105
|
+
store.errorRoute.Head?.() ?? null,
|
|
106
|
+
store.errorRoute.Layout?.() ?? null
|
|
107
|
+
]);
|
|
108
|
+
const pageContext = {
|
|
109
|
+
urlOriginal: req.url,
|
|
110
|
+
urlPathname: originalPathname,
|
|
111
|
+
routeParams: {},
|
|
112
|
+
is404: status === 404,
|
|
113
|
+
is500: status === 500,
|
|
114
|
+
errorMessage: status === 500 && error instanceof Error ? error.message : void 0
|
|
115
|
+
};
|
|
116
|
+
const html = await onRenderHtml({
|
|
117
|
+
pageContext,
|
|
118
|
+
Page: PageModule.Page ?? PageModule.default,
|
|
119
|
+
Layout: LayoutModule ? LayoutModule.Layout ?? LayoutModule.default : void 0,
|
|
120
|
+
Head: HeadModule ? HeadModule.Head ?? HeadModule.default : void 0,
|
|
121
|
+
pageTitleTag: `<title>${status === 404 ? "Page Not Found" : "Server Error"}</title>`,
|
|
122
|
+
serializedContext: serializeContext(pageContext),
|
|
123
|
+
assets: getAssets(store.errorRoute.page)
|
|
124
|
+
});
|
|
125
|
+
return new Response(html, {
|
|
126
|
+
status,
|
|
127
|
+
headers: { "Content-Type": "text/html" }
|
|
128
|
+
});
|
|
129
|
+
} catch (renderError) {
|
|
130
|
+
console.error("Error page render failed:", renderError);
|
|
131
|
+
return new Response(status === 404 ? "Not Found" : "Internal Server Error", { status });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function renderPage(req) {
|
|
135
|
+
const { pathname } = new URL(req.url);
|
|
136
|
+
const isJsonRequest = pathname.endsWith(".pageContext.json");
|
|
137
|
+
let targetPathname = pathname;
|
|
138
|
+
if (isJsonRequest) {
|
|
139
|
+
targetPathname = targetPathname.replace(/\.pageContext\.json$/, "");
|
|
140
|
+
if (targetPathname === "/index") targetPathname = "/";
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const resolved = await buildPageContext(targetPathname, req.url, isJsonRequest);
|
|
144
|
+
if (!resolved) {
|
|
145
|
+
if (isJsonRequest) return Response.json({ is404: true }, { status: 404 });
|
|
146
|
+
return renderErrorPage(req, 404, targetPathname);
|
|
147
|
+
}
|
|
148
|
+
const { pageContext, route, PageModule, HeadModule, LayoutModule } = resolved;
|
|
149
|
+
if (isJsonRequest) return Response.json(pageContext);
|
|
150
|
+
const { onRenderHtml } = await store.config.onRenderHtml();
|
|
151
|
+
const html = await onRenderHtml({
|
|
152
|
+
pageContext,
|
|
153
|
+
Page: PageModule.Page ?? PageModule.default,
|
|
154
|
+
Head: HeadModule ? HeadModule.Head ?? HeadModule.default : void 0,
|
|
155
|
+
Layout: LayoutModule ? LayoutModule.Layout ?? LayoutModule.default : void 0,
|
|
156
|
+
pageTitleTag: pageContext.title ? `<title>${pageContext.title}</title>` : "",
|
|
157
|
+
serializedContext: serializeContext(pageContext),
|
|
158
|
+
assets: getAssets(route.page)
|
|
159
|
+
});
|
|
160
|
+
return new Response(html, {
|
|
161
|
+
status: 200,
|
|
162
|
+
headers: { "Content-Type": "text/html" }
|
|
163
|
+
});
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (error instanceof AbortRender) return new Response(error.reason || "Not Found", { status: error.statusCode });
|
|
166
|
+
console.error("Render Error:", error);
|
|
167
|
+
if (isJsonRequest) return Response.json({ is500: true }, { status: 500 });
|
|
168
|
+
return renderErrorPage(req, 500, targetPathname, error);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
172
|
+
export { renderPage, setVikeState, store };
|
package/dist/shared.mjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/virtuals.ts
|
|
2
|
+
const virtualModuleId = "virtual:routes";
|
|
3
|
+
const virtualManifestId = "virtual:client-manifest";
|
|
4
|
+
const virtualAdapterId = "virtual:vike-lite-adapter";
|
|
5
|
+
const virtualEntryClientId = "virtual:entry-client";
|
|
6
|
+
const virtualSetupId = "virtual:vike-lite/setup";
|
|
7
|
+
//#endregion
|
|
8
|
+
export { virtualSetupId as a, virtualModuleId as i, virtualEntryClientId as n, virtualManifestId as r, virtualAdapterId as t };
|
package/dist/vite.d.mts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Plugin } from "vite";
|
|
2
|
+
|
|
3
|
+
//#region src/vite-plugin.d.ts
|
|
4
|
+
declare function routerPlugin({
|
|
5
|
+
pagesDir,
|
|
6
|
+
serverEntry,
|
|
7
|
+
apiPrefix
|
|
8
|
+
}?: {
|
|
9
|
+
pagesDir?: string | undefined;
|
|
10
|
+
serverEntry?: string | undefined;
|
|
11
|
+
apiPrefix?: string | undefined;
|
|
12
|
+
}): Plugin;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { routerPlugin as default };
|
package/dist/vite.mjs
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { a as virtualSetupId, i as virtualModuleId, n as virtualEntryClientId, r as virtualManifestId, t as virtualAdapterId } from "./virtuals-CR__ZAgP.mjs";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { Readable } from "node:stream";
|
|
5
|
+
import { pipeline } from "node:stream/promises";
|
|
6
|
+
//#region src/vite-plugin.ts
|
|
7
|
+
function generateRoutes(pagesAbsPath) {
|
|
8
|
+
if (!fs.existsSync(pagesAbsPath)) return { routes: [] };
|
|
9
|
+
const routes = [];
|
|
10
|
+
let errorRoute;
|
|
11
|
+
function walk(dir, routePath, parentLayout, parentHead) {
|
|
12
|
+
const files = fs.readdirSync(dir);
|
|
13
|
+
const importPath = "@/pages" + (dir === pagesAbsPath ? "" : "/" + path.relative(pagesAbsPath, dir).replaceAll("\\", "/"));
|
|
14
|
+
const currentLayout = files.includes("+Layout.tsx") ? `${importPath}/+Layout.tsx` : parentLayout;
|
|
15
|
+
const currentHead = files.includes("+Head.tsx") ? `${importPath}/+Head.tsx` : parentHead;
|
|
16
|
+
if (files.includes("+Page.tsx")) {
|
|
17
|
+
const route = {
|
|
18
|
+
path: routePath || "/",
|
|
19
|
+
page: `${importPath}/+Page.tsx`,
|
|
20
|
+
hasData: files.includes("+data.ts"),
|
|
21
|
+
hasTitle: files.includes("+title.ts")
|
|
22
|
+
};
|
|
23
|
+
if (currentLayout) route.layout = currentLayout;
|
|
24
|
+
if (currentHead) route.head = currentHead;
|
|
25
|
+
routes.push(route);
|
|
26
|
+
}
|
|
27
|
+
for (const file of files) {
|
|
28
|
+
const fullPath = path.join(dir, file);
|
|
29
|
+
if (!fs.statSync(fullPath).isDirectory()) continue;
|
|
30
|
+
if (file === "_error") {
|
|
31
|
+
if (fs.readdirSync(fullPath).includes("+Page.tsx")) errorRoute = {
|
|
32
|
+
path: "_error",
|
|
33
|
+
page: `${importPath}/_error/+Page.tsx`,
|
|
34
|
+
layout: currentLayout,
|
|
35
|
+
head: currentHead
|
|
36
|
+
};
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (file.startsWith("_")) continue;
|
|
40
|
+
walk(fullPath, routePath + (routePath.endsWith("/") ? "" : "/") + file.replace(/^@/, ":"), currentLayout, currentHead);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
walk(pagesAbsPath, "");
|
|
44
|
+
routes.sort((a, b) => {
|
|
45
|
+
const aDynamic = a.path.includes(":");
|
|
46
|
+
const bDynamic = b.path.includes(":");
|
|
47
|
+
if (aDynamic && !bDynamic) return 1;
|
|
48
|
+
if (!aDynamic && bDynamic) return -1;
|
|
49
|
+
return b.path.length - a.path.length;
|
|
50
|
+
});
|
|
51
|
+
const homeIndex = routes.findIndex((r) => r.path === "/index");
|
|
52
|
+
if (homeIndex !== -1) routes[homeIndex].path = "/";
|
|
53
|
+
return {
|
|
54
|
+
routes,
|
|
55
|
+
errorRoute
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Anti-FOUC: Inspect all known SSR modules, ask the client environment
|
|
60
|
+
* to translate them into plain text (thanks to ?direct) and return them.
|
|
61
|
+
*/
|
|
62
|
+
async function injectFOUCStyles(server, html) {
|
|
63
|
+
const styles = /* @__PURE__ */ new Set();
|
|
64
|
+
const ssrEnv = server.environments.ssr;
|
|
65
|
+
const clientEnv = server.environments.client;
|
|
66
|
+
for (const mod of ssrEnv.moduleGraph.idToModuleMap.values()) {
|
|
67
|
+
if (!(mod.file && /\.(css|scss|sass|less|styl|stylus)($|\?)/.test(mod.file))) continue;
|
|
68
|
+
const url = mod.url.split("?", 1)[0];
|
|
69
|
+
try {
|
|
70
|
+
const result = await clientEnv.transformRequest(url + "?direct");
|
|
71
|
+
if (result?.code) styles.add(result.code);
|
|
72
|
+
} catch {}
|
|
73
|
+
}
|
|
74
|
+
if (styles.size === 0) return html;
|
|
75
|
+
const headEndIndex = html.lastIndexOf("</head>");
|
|
76
|
+
const bodyEndIndex = html.lastIndexOf("</body>");
|
|
77
|
+
let cssContent = "";
|
|
78
|
+
for (const s of styles) cssContent += s;
|
|
79
|
+
const inlineCss = `<style type="text/css" data-vite-dev-fouc>${cssContent}</style>`;
|
|
80
|
+
return html.slice(0, headEndIndex) + inlineCss + html.slice(headEndIndex, bodyEndIndex) + `<script type="module" data-vite-dev-fouc-cleanup>requestAnimationFrame(()=>{document.querySelectorAll('[data-vite-dev-fouc]').forEach(el=>el.remove());document.currentScript?.remove();})<\/script>` + html.slice(bodyEndIndex);
|
|
81
|
+
}
|
|
82
|
+
function routerPlugin({ pagesDir = "pages", serverEntry = "server/index.ts", apiPrefix = "/api" } = {}) {
|
|
83
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
84
|
+
let viteConfigRoot;
|
|
85
|
+
const resolvedVirtualModuleId = "\0" + virtualModuleId;
|
|
86
|
+
const resolvedVirtualManifestId = "\0" + virtualManifestId;
|
|
87
|
+
const resolvedVirtualEntryClientId = "\0" + virtualEntryClientId;
|
|
88
|
+
const resolvedVirtualSetupId = "\0" + virtualSetupId;
|
|
89
|
+
return {
|
|
90
|
+
name: "vike-lite",
|
|
91
|
+
config() {
|
|
92
|
+
return {
|
|
93
|
+
appType: "custom",
|
|
94
|
+
environments: {
|
|
95
|
+
client: { build: {
|
|
96
|
+
outDir: "../dist/client",
|
|
97
|
+
emptyOutDir: true,
|
|
98
|
+
cssMinify: true,
|
|
99
|
+
manifest: true,
|
|
100
|
+
rolldownOptions: {
|
|
101
|
+
input: virtualEntryClientId,
|
|
102
|
+
output: {
|
|
103
|
+
format: "esm",
|
|
104
|
+
entryFileNames: "assets/[name].[hash].js",
|
|
105
|
+
chunkFileNames: (chunkInfo) => {
|
|
106
|
+
if (chunkInfo.facadeModuleId?.includes(`/${pagesDir}/`)) return "assets/pages/[name].[hash].js";
|
|
107
|
+
return "assets/chunks/[name].[hash].js";
|
|
108
|
+
},
|
|
109
|
+
assetFileNames: "assets/static/[name].[hash][extname]"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} },
|
|
113
|
+
ssr: { build: {
|
|
114
|
+
target: "esnext",
|
|
115
|
+
outDir: "../dist/server",
|
|
116
|
+
emptyOutDir: true,
|
|
117
|
+
rolldownOptions: {
|
|
118
|
+
input: serverEntry,
|
|
119
|
+
output: {
|
|
120
|
+
format: "esm",
|
|
121
|
+
entryFileNames: "index.mjs",
|
|
122
|
+
chunkFileNames: (chunkInfo) => {
|
|
123
|
+
if (chunkInfo.facadeModuleId?.includes(`/${pagesDir}/`)) return "assets/pages/[name].[hash].mjs";
|
|
124
|
+
return "assets/chunks/[name].[hash].mjs";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} }
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
configResolved(config) {
|
|
133
|
+
viteConfigRoot = config.root;
|
|
134
|
+
},
|
|
135
|
+
resolveId(id) {
|
|
136
|
+
if (id === "virtual:routes") return resolvedVirtualModuleId;
|
|
137
|
+
if (id === "virtual:client-manifest") return resolvedVirtualManifestId;
|
|
138
|
+
if (id === "virtual:entry-client") return resolvedVirtualEntryClientId;
|
|
139
|
+
if (id === "virtual:vike-lite/setup") return resolvedVirtualSetupId;
|
|
140
|
+
},
|
|
141
|
+
async load(id, options) {
|
|
142
|
+
if (id === resolvedVirtualModuleId) {
|
|
143
|
+
const { routes, errorRoute } = generateRoutes(path.resolve(viteConfigRoot, pagesDir));
|
|
144
|
+
const isSSR = options.ssr;
|
|
145
|
+
let code = `import {onRenderClient,onRenderHtml} from '${virtualAdapterId}';export const config={onRenderClient,onRenderHtml};export const routes=[`;
|
|
146
|
+
for (const r of routes) {
|
|
147
|
+
code += `{path:'${r.path}',page:'${r.page}',hasData:${r.hasData},hasTitle:${r.hasTitle},Page:()=>import('${r.page}'),`;
|
|
148
|
+
if (r.head) code += `Head:()=>import('${r.head}'),`;
|
|
149
|
+
if (r.layout) code += `Layout:()=>import('${r.layout}'),`;
|
|
150
|
+
if (isSSR) {
|
|
151
|
+
if (r.hasData) code += `data:()=>import('${r.page.replace("+Page.tsx", "+data.ts")}'),`;
|
|
152
|
+
if (r.hasTitle) code += `title:()=>import('${r.page.replace("+Page.tsx", "+title.ts")}'),`;
|
|
153
|
+
}
|
|
154
|
+
code += "},";
|
|
155
|
+
}
|
|
156
|
+
code += "];";
|
|
157
|
+
if (errorRoute) {
|
|
158
|
+
const e = errorRoute;
|
|
159
|
+
code += `export const errorRoute={path:'${e.path}',page:'${e.page}',Page:()=>import('${e.page}'),`;
|
|
160
|
+
if (e.layout) code += `Layout:()=>import('${e.layout}'),`;
|
|
161
|
+
if (e.head) code += `Head:()=>import('${e.head}'),`;
|
|
162
|
+
code += "};";
|
|
163
|
+
} else code += "export const errorRoute=null;";
|
|
164
|
+
return code;
|
|
165
|
+
}
|
|
166
|
+
if (id === resolvedVirtualManifestId) {
|
|
167
|
+
if (!isProd || !options?.ssr) return "";
|
|
168
|
+
const manifestPath = path.join(viteConfigRoot, "../dist/client/.vite/manifest.json");
|
|
169
|
+
return `export default ${fs.readFileSync(manifestPath, "utf8")}`;
|
|
170
|
+
}
|
|
171
|
+
if (id === resolvedVirtualEntryClientId) return `import{onRenderClient}from'${virtualAdapterId}';onRenderClient();`;
|
|
172
|
+
if (id === resolvedVirtualManifestId) {
|
|
173
|
+
if (!isProd || !options?.ssr) return "export default {}";
|
|
174
|
+
const manifestPath = path.join(viteConfigRoot, "../dist/client/.vite/manifest.json");
|
|
175
|
+
return `export default ${fs.readFileSync(manifestPath, "utf8")}`;
|
|
176
|
+
}
|
|
177
|
+
if (id === resolvedVirtualSetupId) return `
|
|
178
|
+
// 1. Importa i dati virtuali generati da Vite
|
|
179
|
+
import { routes, errorRoute, config } from '${virtualModuleId}';
|
|
180
|
+
|
|
181
|
+
// 2. Importa il setter dal TUO pacchetto compilato (assicurati di esportarlo nel package.json)
|
|
182
|
+
import { setVikeState } from 'vike-lite/store';
|
|
183
|
+
|
|
184
|
+
// 3. Risolve il manifest per la build di produzione
|
|
185
|
+
let manifest;
|
|
186
|
+
if (process.env.NODE_ENV === 'production') {
|
|
187
|
+
manifest = (await import('${virtualManifestId}')).default;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 4. INIETTA lo stato in memoria!
|
|
191
|
+
setVikeState({ routes, errorRoute, config, manifest });
|
|
192
|
+
`;
|
|
193
|
+
},
|
|
194
|
+
configureServer(server) {
|
|
195
|
+
return () => {
|
|
196
|
+
server.middlewares.use(async (req, res, next) => {
|
|
197
|
+
try {
|
|
198
|
+
const ssrEnv = server.environments.ssr;
|
|
199
|
+
const absoluteServerEntry = path.join(viteConfigRoot, serverEntry);
|
|
200
|
+
const { default: app } = await ssrEnv.runner.import(absoluteServerEntry);
|
|
201
|
+
const headers = new Headers();
|
|
202
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
203
|
+
if (key.startsWith(":")) continue;
|
|
204
|
+
if (Array.isArray(value)) for (const v of value) headers.append(key, v);
|
|
205
|
+
else if (value !== void 0) headers.set(key, value);
|
|
206
|
+
}
|
|
207
|
+
const requestInit = {
|
|
208
|
+
method: req.method,
|
|
209
|
+
headers
|
|
210
|
+
};
|
|
211
|
+
if (req.url.startsWith(apiPrefix)) {
|
|
212
|
+
server.config.logger.info(`API request: ${req.method} ${req.url}`, { timestamp: true });
|
|
213
|
+
requestInit.body = Readable.toWeb(req);
|
|
214
|
+
requestInit.duplex = "half";
|
|
215
|
+
} else if (req.url.endsWith(".pageContext.json")) server.config.logger.info(`SPA Navigation request: ${req.url}`, { timestamp: true });
|
|
216
|
+
const response = await app.fetch(new Request(`http://${req.headers.host}${req.url}`, requestInit));
|
|
217
|
+
res.statusCode = response.status;
|
|
218
|
+
if (response.headers.get("content-type")?.includes("text/html")) {
|
|
219
|
+
server.config.logger.info(`Page request: ${req.url}`, { timestamp: true });
|
|
220
|
+
let html = await response.text();
|
|
221
|
+
html = await injectFOUCStyles(server, html);
|
|
222
|
+
html = await server.transformIndexHtml(req.url, html);
|
|
223
|
+
res.setHeader("Content-Type", "text/html");
|
|
224
|
+
res.end(html);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
for (const [key, value] of response.headers) res.setHeader(key, value);
|
|
228
|
+
if (!response.body) {
|
|
229
|
+
res.end();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (res.destroyed || res.closed) return;
|
|
233
|
+
try {
|
|
234
|
+
await pipeline(Readable.fromWeb(response.body), res);
|
|
235
|
+
} catch {}
|
|
236
|
+
} catch (error) {
|
|
237
|
+
next(error);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
//#endregion
|
|
245
|
+
export { routerPlugin as default };
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vike-lite",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.mts",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./vite": {
|
|
10
|
+
"import": "./dist/vite.mjs",
|
|
11
|
+
"types": "./dist/vite.d.mts"
|
|
12
|
+
},
|
|
13
|
+
"./server": {
|
|
14
|
+
"import": "./dist/server.mjs",
|
|
15
|
+
"types": "./dist/server.d.mts"
|
|
16
|
+
},
|
|
17
|
+
"./shared": {
|
|
18
|
+
"import": "./dist/shared.mjs",
|
|
19
|
+
"types": "./dist/shared.d.mts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/node-ecosystem/vike-lite.git"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/node-ecosystem/vike-lite#readme",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/node-ecosystem/vike-lite/issues"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"lint": "eslint src/**/*.{ts,tsx} *.ts",
|
|
35
|
+
"build": "tsdown",
|
|
36
|
+
"od": "yarn outdated"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^26.0.0",
|
|
40
|
+
"@yarnpkg/sdks": "^3.3.0",
|
|
41
|
+
"eslint": "^10.5.0",
|
|
42
|
+
"eslint-plugin-unicorn": "^68.0.0",
|
|
43
|
+
"tsdown": "^0.22.3",
|
|
44
|
+
"typescript": "^6.0.3",
|
|
45
|
+
"typescript-eslint": "^8.62.0",
|
|
46
|
+
"vite": "^8.1.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"vite": ">=8"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=22"
|
|
53
|
+
},
|
|
54
|
+
"packageManager": "yarn@4.17.0"
|
|
55
|
+
}
|