vike-lite 1.14.1 → 1.15.1
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 +5 -5
- package/dist/__internal/vite/defaultServerEntry.mjs +83 -0
- package/dist/{server-BkIWABsi.mjs → server-BITt1YvS.mjs} +11 -4
- package/dist/server.d.mts +1 -0
- package/dist/server.mjs +1 -1
- package/dist/vite.d.mts +9 -8
- package/dist/vite.mjs +25 -20
- package/package.json +33 -10
package/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Vike Lite
|
|
2
|
+
<a href="https://npmjs.com/package/vike-lite"><img src="https://img.shields.io/npm/v/vike-lite.svg" alt="npm package"></a>
|
|
2
3
|
|
|
3
4
|
A lightweight, fast, and minimal framework for Server-Side Rendering (SSR) and Static Site Generation (SSG) inspired by [Vike](https://vike.dev).
|
|
4
5
|
|
|
@@ -27,9 +28,9 @@ export default {
|
|
|
27
28
|
plugins: [
|
|
28
29
|
vikeLite({
|
|
29
30
|
pagesDir: 'pages', // Default: Directory containing your pages
|
|
30
|
-
serverEntry: 'server/index', // Default: Custom server entry file
|
|
31
31
|
apiPrefix: '/api', // Default: Prefix to bypass SSR for API routes
|
|
32
32
|
prerender: false // Default: Enable SSG globally
|
|
33
|
+
serverEntry: 'server/index', // Default: unfedined value that allows to use a custom server entry file
|
|
33
34
|
})
|
|
34
35
|
]
|
|
35
36
|
} satisfies UserConfig
|
|
@@ -61,9 +62,8 @@ app.route('/api', apiRoutes)
|
|
|
61
62
|
|
|
62
63
|
// 2. Catch-all remaining requests and pass them to vike-lite
|
|
63
64
|
app.get('*', async (c, next) => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return response ?? next()
|
|
65
|
+
// renderPage will return a Node.js Response
|
|
66
|
+
return await renderPage(c.req.raw)
|
|
67
67
|
})
|
|
68
68
|
|
|
69
69
|
// 3. Error Handling
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { renderPage } from 'vike-lite/server'
|
|
2
|
+
import { createServer } from 'node:http'
|
|
3
|
+
import { Readable } from 'node:stream'
|
|
4
|
+
import { pipeline } from 'node:stream/promises'
|
|
5
|
+
import fs from 'node:fs'
|
|
6
|
+
import fsPromises from 'node:fs/promises'
|
|
7
|
+
import path from 'node:path'
|
|
8
|
+
import { fileURLToPath } from 'node:url'
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
10
|
+
const clientDir = path.resolve(__dirname, '../client')
|
|
11
|
+
const MIME_TYPES = {
|
|
12
|
+
'.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8',
|
|
13
|
+
'.mjs': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8',
|
|
14
|
+
'.json': 'application/json; charset=utf-8', '.xml': 'application/xml',
|
|
15
|
+
'.txt': 'text/plain; charset=utf-8', '.map': 'application/json; charset=utf-8',
|
|
16
|
+
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
|
|
17
|
+
'.gif': 'image/gif', '.svg': 'image/svg+xml', '.ico': 'image/x-icon',
|
|
18
|
+
'.webp': 'image/webp', '.avif': 'image/avif', '.woff': 'font/woff',
|
|
19
|
+
'.woff2': 'font/woff2', '.ttf': 'font/ttf', '.otf': 'font/otf',
|
|
20
|
+
'.mp4': 'video/mp4', '.webm': 'video/webm', '.mp3': 'audio/mpeg',
|
|
21
|
+
'.pdf': 'application/pdf', '.wasm': 'application/wasm',
|
|
22
|
+
'.webmanifest': 'application/manifest+json'
|
|
23
|
+
}
|
|
24
|
+
const server = createServer(async (req, res) => {
|
|
25
|
+
try {
|
|
26
|
+
const urlObj = new URL(req.url || '/', 'http://' + (req.headers.host || 'localhost'))
|
|
27
|
+
const pathname = urlObj.pathname
|
|
28
|
+
let fileUrl = pathname
|
|
29
|
+
const { BASE_URL } = import.meta.env
|
|
30
|
+
if (BASE_URL !== '/' && pathname.startsWith(BASE_URL)) fileUrl = '/' + pathname.slice(BASE_URL.length)
|
|
31
|
+
if (fileUrl !== '/') {
|
|
32
|
+
const filePath = path.resolve(clientDir, '.' + fileUrl)
|
|
33
|
+
if (!filePath.startsWith(clientDir)) {
|
|
34
|
+
res.statusCode = 403
|
|
35
|
+
res.end('Forbidden')
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const stat = await fsPromises.stat(filePath)
|
|
40
|
+
if (stat.isFile()) {
|
|
41
|
+
const ext = path.extname(filePath).toLowerCase()
|
|
42
|
+
const mimeType = MIME_TYPES[ext] || 'application/octet-stream'
|
|
43
|
+
res.setHeader('Content-Type', mimeType)
|
|
44
|
+
res.setHeader(
|
|
45
|
+
'Cache-Control',
|
|
46
|
+
fileUrl.startsWith('/assets/') ? 'public, max-age=31536000, immutable' : 'public, max-age=0, must-revalidate'
|
|
47
|
+
)
|
|
48
|
+
fs.createReadStream(filePath).pipe(res)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
} catch { }
|
|
52
|
+
}
|
|
53
|
+
const headers = new Headers()
|
|
54
|
+
for (const [key, val] of Object.entries(req.headers)) {
|
|
55
|
+
if (Array.isArray(val)) for (const v of val) headers.append(key, v)
|
|
56
|
+
else if (val) headers.set(key, val)
|
|
57
|
+
}
|
|
58
|
+
const { method } = req
|
|
59
|
+
const init = { method, headers }
|
|
60
|
+
if (method !== 'GET' && method !== 'HEAD') {
|
|
61
|
+
init.body = Readable.toWeb(req)
|
|
62
|
+
init.duplex = 'half'
|
|
63
|
+
}
|
|
64
|
+
const request = new Request(urlObj.href, init)
|
|
65
|
+
const response = await renderPage(request)
|
|
66
|
+
res.statusCode = response.status
|
|
67
|
+
for (const [key, val] of response.headers) res.setHeader(key, val)
|
|
68
|
+
if (method === 'HEAD' || !response.body) {
|
|
69
|
+
await response.body?.cancel()
|
|
70
|
+
res.end()
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
try { await pipeline(Readable.fromWeb(response.body), res) } catch (error) { console.error('Stream error:', error) }
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error('Handler error:', error)
|
|
76
|
+
if (!res.headersSent) {
|
|
77
|
+
res.statusCode = 500
|
|
78
|
+
res.end('Internal Server Error')
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
const port = process.env.PORT ? Number.parseInt(process.env.PORT) : 3000
|
|
83
|
+
server.listen(port, () => { console.log(`\n\u{1B}[32m🚀 Server is running at http://localhost:${port}\u{1B}[0m\n`) })
|
|
@@ -117,7 +117,11 @@ async function renderErrorPage(req, status, urlPathname, error, nonce) {
|
|
|
117
117
|
errorMessage = isProd ? "Internal Server Error" : error instanceof Error ? error.message : "Unknown error";
|
|
118
118
|
is500 = true;
|
|
119
119
|
} else is500 = false;
|
|
120
|
-
|
|
120
|
+
const fallbackText = status === 404 ? "Not Found" : "Internal Server Error";
|
|
121
|
+
if (!store.errorRoute) return new Response(fallbackText, {
|
|
122
|
+
status,
|
|
123
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
124
|
+
});
|
|
121
125
|
try {
|
|
122
126
|
const [PageModule, HeadModule, LayoutModule] = await Promise.all([
|
|
123
127
|
store.errorRoute.Page(),
|
|
@@ -144,11 +148,14 @@ async function renderErrorPage(req, status, urlPathname, error, nonce) {
|
|
|
144
148
|
});
|
|
145
149
|
return new Response(html, {
|
|
146
150
|
status,
|
|
147
|
-
headers: { "Content-Type": "text/html" }
|
|
151
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
148
152
|
});
|
|
149
153
|
} catch (renderError) {
|
|
150
154
|
console.error("[vike-lite] Error page render failed:", renderError);
|
|
151
|
-
return new Response(
|
|
155
|
+
return new Response(fallbackText, {
|
|
156
|
+
status,
|
|
157
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
158
|
+
});
|
|
152
159
|
}
|
|
153
160
|
}
|
|
154
161
|
async function renderPage(req, { nonce } = {}) {
|
|
@@ -185,7 +192,7 @@ async function renderPage(req, { nonce } = {}) {
|
|
|
185
192
|
});
|
|
186
193
|
return new Response(html, {
|
|
187
194
|
status: 200,
|
|
188
|
-
headers: { "Content-Type": "text/html" }
|
|
195
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
189
196
|
});
|
|
190
197
|
} catch (error) {
|
|
191
198
|
if (error instanceof AbortRedirect) {
|
package/dist/server.d.mts
CHANGED
package/dist/server.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as renderPage } from "./server-
|
|
1
|
+
import { t as renderPage } from "./server-BITt1YvS.mjs";
|
|
2
2
|
export { renderPage };
|
package/dist/vite.d.mts
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { Plugin } from "vite";
|
|
2
2
|
//#region src/vite/index.d.ts
|
|
3
|
-
declare function vikeLite({ pagesDir,
|
|
3
|
+
declare function vikeLite({ pagesDir, apiPrefix, prerender, serverEntry }?: {
|
|
4
4
|
/**
|
|
5
5
|
* The directory where your page components are located.
|
|
6
6
|
* This is where the plugin will look for your page files to generate routes.
|
|
7
7
|
* @default 'pages'
|
|
8
8
|
*/
|
|
9
9
|
pagesDir?: string;
|
|
10
|
-
/**
|
|
11
|
-
* The entry point for your server application code.
|
|
12
|
-
* This is where you can define custom server logic, such as API routes or middleware.
|
|
13
|
-
* The build will produce dist/server/index.mjs, which is the entry point for your server application.
|
|
14
|
-
* @default 'server/index'
|
|
15
|
-
*/
|
|
16
|
-
serverEntry?: string;
|
|
17
10
|
/**
|
|
18
11
|
* The prefix for your API routes.
|
|
19
12
|
* @default '/api'
|
|
@@ -25,6 +18,14 @@ declare function vikeLite({ pagesDir, serverEntry, apiPrefix, prerender }?: {
|
|
|
25
18
|
* @default false
|
|
26
19
|
*/
|
|
27
20
|
prerender?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The entry point for your server application code.
|
|
23
|
+
* This is where you can define custom server logic, such as API routes or middleware.
|
|
24
|
+
* The build will produce dist/server/index.mjs, which is the entry point for your server application.
|
|
25
|
+
* If false disable the server entry.
|
|
26
|
+
* @default undefined
|
|
27
|
+
*/
|
|
28
|
+
serverEntry?: string | false;
|
|
28
29
|
}): Plugin;
|
|
29
30
|
//#endregion
|
|
30
31
|
export { vikeLite as default };
|
package/dist/vite.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as renderPage } from "./server-
|
|
1
|
+
import { t as renderPage } from "./server-BITt1YvS.mjs";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Readable } from "node:stream";
|
|
@@ -103,7 +103,7 @@ const SUPPORTED_RENDERERS = [
|
|
|
103
103
|
];
|
|
104
104
|
//#endregion
|
|
105
105
|
//#region src/vite/index.ts
|
|
106
|
-
function vikeLite({ pagesDir = "pages",
|
|
106
|
+
function vikeLite({ pagesDir = "pages", apiPrefix = "/api", prerender = false, serverEntry } = {}) {
|
|
107
107
|
const isProd = process.env.NODE_ENV === "production";
|
|
108
108
|
let viteConfigRoot;
|
|
109
109
|
let outDir;
|
|
@@ -120,6 +120,7 @@ function vikeLite({ pagesDir = "pages", serverEntry = "server/index", apiPrefix
|
|
|
120
120
|
};
|
|
121
121
|
const VIRTUAL_VALUES = new Set(Object.values(VIRTUAL));
|
|
122
122
|
const RESOLVED = Object.fromEntries(Object.entries(VIRTUAL).map(([k, v]) => [k, `\0${v}`]));
|
|
123
|
+
const importSetup = `import'${VIRTUAL.setup}';`;
|
|
123
124
|
return {
|
|
124
125
|
name: "vike-lite",
|
|
125
126
|
config(config, { mode }) {
|
|
@@ -127,7 +128,7 @@ function vikeLite({ pagesDir = "pages", serverEntry = "server/index", apiPrefix
|
|
|
127
128
|
for (const key in envVariables) if (process.env[key] === void 0) process.env[key] = envVariables[key];
|
|
128
129
|
outDir = config.build?.outDir ?? "dist";
|
|
129
130
|
const { emptyOutDir, minify = true, cssMinify = true, sourcemap } = config.build || {};
|
|
130
|
-
viteConfigRoot = config.root ? path.resolve(config.root) : process.cwd();
|
|
131
|
+
viteConfigRoot = (config.root ? path.resolve(config.root) : process.cwd()).replaceAll("\\", "/");
|
|
131
132
|
const { routes } = generateRoutes(viteConfigRoot, pagesDir);
|
|
132
133
|
hasAnyPrerender = prerender || routes.some((r) => r.prerender);
|
|
133
134
|
return {
|
|
@@ -247,7 +248,8 @@ function vikeLite({ pagesDir = "pages", serverEntry = "server/index", apiPrefix
|
|
|
247
248
|
return code;
|
|
248
249
|
}
|
|
249
250
|
if (id === RESOLVED.manifest) {
|
|
250
|
-
|
|
251
|
+
const isSSR = options.ssr;
|
|
252
|
+
if (!isProd || !isSSR) return "export default{}";
|
|
251
253
|
const manifestPath = path.join(viteConfigRoot, outDir, "client/.vite/manifest.json");
|
|
252
254
|
return `export default ${fs.readFileSync(manifestPath, "utf8")}`;
|
|
253
255
|
}
|
|
@@ -258,28 +260,31 @@ function vikeLite({ pagesDir = "pages", serverEntry = "server/index", apiPrefix
|
|
|
258
260
|
}
|
|
259
261
|
if (id === RESOLVED.entryServer) {
|
|
260
262
|
if (serverEntry) {
|
|
261
|
-
const basePath = path.
|
|
263
|
+
const basePath = path.join(viteConfigRoot, serverEntry);
|
|
262
264
|
let serverEntryPath = "";
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
265
|
+
if (!fs.existsSync(basePath)) {
|
|
266
|
+
for (const ext of [
|
|
267
|
+
".ts",
|
|
268
|
+
".js",
|
|
269
|
+
".mjs"
|
|
270
|
+
]) if (fs.existsSync(basePath + ext)) {
|
|
271
|
+
serverEntryPath = basePath + ext;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
if (!serverEntryPath) throw new Error(`[vike-lite] serverEntry ${serverEntry} file not found`);
|
|
270
275
|
}
|
|
271
|
-
|
|
272
|
-
return `import '${VIRTUAL.setup}';export * from'${serverEntryPath}';export{default}from'${serverEntryPath}';`;
|
|
276
|
+
return importSetup + `export*from'${serverEntryPath}';export{default}from'${serverEntryPath}';`;
|
|
273
277
|
}
|
|
274
|
-
|
|
275
|
-
|
|
278
|
+
if (serverEntry === false) return importSetup + "import{renderPage}from'vike-lite/server';export default{fetch:renderPage};";
|
|
279
|
+
const defaultServerEntryContent = isProd ? fs.readFileSync(path.resolve("vike-lite/__internal/vite/defaultServerEntry.mjs"), "utf8") : `import{renderPage}from'vike-lite/server';`;
|
|
280
|
+
return importSetup + defaultServerEntryContent + "export default{fetch:renderPage};";
|
|
276
281
|
}
|
|
277
|
-
if (id === RESOLVED.entryPrerender) return `
|
|
282
|
+
if (id === RESOLVED.entryPrerender) return importSetup + `export{routes}from'${VIRTUAL.routes}';`;
|
|
278
283
|
},
|
|
279
284
|
async closeBundle() {
|
|
280
285
|
if (!isProd || this.environment.name !== "ssr") return;
|
|
281
286
|
const { pathToFileURL } = await import("node:url");
|
|
282
|
-
const prerenderPath = path.
|
|
287
|
+
const prerenderPath = path.join(viteConfigRoot, outDir, "server/prerender.mjs");
|
|
283
288
|
if (!fs.existsSync(prerenderPath)) return;
|
|
284
289
|
const { routes } = await import(pathToFileURL(prerenderPath).href);
|
|
285
290
|
if (!routes) return;
|
|
@@ -312,7 +317,7 @@ function vikeLite({ pagesDir = "pages", serverEntry = "server/index", apiPrefix
|
|
|
312
317
|
const { BASE_URL } = import.meta.env;
|
|
313
318
|
const baseNoSlash = BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL;
|
|
314
319
|
let generatedCount = 0;
|
|
315
|
-
const clientDir = path.
|
|
320
|
+
const clientDir = path.join(viteConfigRoot, outDir, "client");
|
|
316
321
|
for (const urlPath of urlsToPrerender) {
|
|
317
322
|
const htmlRes = await renderPage(new Request(`http://localhost${baseNoSlash}${urlPath}`));
|
|
318
323
|
if (htmlRes?.ok && htmlRes.headers.get("content-type")?.includes("text/html")) {
|
|
@@ -334,7 +339,7 @@ function vikeLite({ pagesDir = "pages", serverEntry = "server/index", apiPrefix
|
|
|
334
339
|
},
|
|
335
340
|
configureServer(server) {
|
|
336
341
|
return () => {
|
|
337
|
-
const pagesPath = path.
|
|
342
|
+
const pagesPath = path.join(viteConfigRoot, pagesDir);
|
|
338
343
|
server.watcher.on("all", (event, file) => {
|
|
339
344
|
if (!((event === "add" || event === "unlink") && file.startsWith(pagesPath))) return;
|
|
340
345
|
for (const env of Object.values(server.environments)) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vike-lite",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.1",
|
|
4
4
|
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/node-ecosystem/vike-lite.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/node-ecosystem/vike-lite#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/node-ecosystem/vike-lite/issues"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"framework",
|
|
15
|
+
"lite",
|
|
16
|
+
"react",
|
|
17
|
+
"react-ssr",
|
|
18
|
+
"server-side-rendering",
|
|
19
|
+
"solid",
|
|
20
|
+
"solid-js",
|
|
21
|
+
"solid-ssr",
|
|
22
|
+
"solidjs",
|
|
23
|
+
"ssg",
|
|
24
|
+
"ssr",
|
|
25
|
+
"static-site-generator",
|
|
26
|
+
"vike",
|
|
27
|
+
"vike-lite",
|
|
28
|
+
"vite",
|
|
29
|
+
"vite-plugin",
|
|
30
|
+
"vite-ssr",
|
|
31
|
+
"vitejs",
|
|
32
|
+
"vue",
|
|
33
|
+
"vue-ssr",
|
|
34
|
+
"web-framework"
|
|
35
|
+
],
|
|
5
36
|
"type": "module",
|
|
6
37
|
"module": "./dist/index.mjs",
|
|
7
38
|
"types": "./dist/index.d.mts",
|
|
@@ -34,14 +65,6 @@
|
|
|
34
65
|
"import": "./dist/__internal/server.mjs"
|
|
35
66
|
}
|
|
36
67
|
},
|
|
37
|
-
"repository": {
|
|
38
|
-
"type": "git",
|
|
39
|
-
"url": "git+https://github.com/node-ecosystem/vike-lite.git"
|
|
40
|
-
},
|
|
41
|
-
"homepage": "https://github.com/node-ecosystem/vike-lite#readme",
|
|
42
|
-
"bugs": {
|
|
43
|
-
"url": "https://github.com/node-ecosystem/vike-lite/issues"
|
|
44
|
-
},
|
|
45
68
|
"files": [
|
|
46
69
|
"dist"
|
|
47
70
|
],
|
|
@@ -51,7 +74,7 @@
|
|
|
51
74
|
},
|
|
52
75
|
"devDependencies": {
|
|
53
76
|
"@types/node": "^26.1.1",
|
|
54
|
-
"tsdown": "^0.22.
|
|
77
|
+
"tsdown": "^0.22.8",
|
|
55
78
|
"vite": "^8.1.4"
|
|
56
79
|
},
|
|
57
80
|
"peerDependencies": {
|