vike-lite 1.5.0 → 1.6.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.
@@ -2,8 +2,8 @@ import { t as Config } from "../index-Dt5n1zWh.mjs";
2
2
 
3
3
  //#region src/server/store.d.ts
4
4
  interface VikeState {
5
- routes: any[];
6
- errorRoute: any | null;
5
+ routes: typeof import('virtual:routes')['routes'];
6
+ errorRoute: typeof import('virtual:routes')['errorRoute'] | null;
7
7
  config: Config | null;
8
8
  manifest: Manifest | undefined;
9
9
  }
package/dist/server.mjs CHANGED
@@ -26,11 +26,15 @@ var AbortRender = class extends Error {
26
26
  //#endregion
27
27
  //#region src/server/renderPage.ts
28
28
  const isProd = process.env.NODE_ENV === "production";
29
+ const { BASE_URL } = import.meta.env;
30
+ function withBase(file) {
31
+ return `${BASE_URL.replace(/\/$/, "")}/${file.replace(/^\//, "")}`;
32
+ }
29
33
  function getAssets(route) {
30
34
  if (!isProd) return {
31
35
  cssLinks: "",
32
36
  jsPreloads: "",
33
- entryClient: "/@id/virtual:entry-client"
37
+ entryClient: withBase("@id/virtual:entry-client")
34
38
  };
35
39
  const cssFiles = /* @__PURE__ */ new Set();
36
40
  const jsFiles = /* @__PURE__ */ new Set();
@@ -55,9 +59,9 @@ function getAssets(route) {
55
59
  if (head) collectAssets(head);
56
60
  if (layout) collectAssets(layout);
57
61
  return {
58
- cssLinks: [...cssFiles].map((href) => `<link rel="stylesheet" href="/${href}">`).join(""),
59
- jsPreloads: [...jsFiles].map((href) => `<link rel="modulepreload" href="/${href}">`).join(""),
60
- entryClient: `/${manifest[virtualEntryClientKey].file}`
62
+ cssLinks: [...cssFiles].map((href) => `<link rel="stylesheet" href="${withBase(href)}">`).join(""),
63
+ jsPreloads: [...jsFiles].map((href) => `<link rel="modulepreload" href="${withBase(href)}">`).join(""),
64
+ entryClient: withBase(manifest[virtualEntryClientKey].file)
61
65
  };
62
66
  }
63
67
  async function buildPageContext(urlPathname, urlOriginal, isJsonRequest) {
@@ -70,8 +74,8 @@ async function buildPageContext(urlPathname, urlOriginal, isJsonRequest) {
70
74
  urlPathname
71
75
  };
72
76
  const [dataMod, titleMod, PageModule, HeadModule, LayoutModule] = await Promise.all([
73
- route.data?.() ?? null,
74
- route.title?.() ?? null,
77
+ route.Data?.() ?? null,
78
+ route.Title?.() ?? null,
75
79
  isJsonRequest ? null : route.Page(),
76
80
  isJsonRequest ? null : route.Head?.() ?? null,
77
81
  isJsonRequest ? null : route.Layout?.() ?? null
@@ -133,7 +137,12 @@ async function renderErrorPage(req, status, urlPathname, error) {
133
137
  }
134
138
  }
135
139
  async function renderPage(req) {
136
- const { pathname } = new URL(req.url);
140
+ let pathname = new URL(req.url).pathname;
141
+ if (BASE_URL !== "/") {
142
+ const baseSlashed = BASE_URL.endsWith("/") ? BASE_URL : BASE_URL + "/";
143
+ const baseNoSlash = baseSlashed.slice(0, -1);
144
+ pathname = pathname === baseNoSlash ? "/" : pathname.slice(baseSlashed.length - 1);
145
+ }
137
146
  const isJsonRequest = pathname.endsWith(".pageContext.json");
138
147
  let targetPathname = pathname;
139
148
  if (isJsonRequest) {
package/dist/vite.d.mts CHANGED
@@ -6,9 +6,24 @@ declare function routerPlugin({
6
6
  serverEntry,
7
7
  apiPrefix
8
8
  }?: {
9
- pagesDir?: string | undefined;
10
- serverEntry?: string | undefined;
11
- apiPrefix?: string | undefined;
9
+ /**
10
+ * The directory where your page components are located.
11
+ * This is where the plugin will look for your page files to generate routes.
12
+ * @default 'pages'
13
+ */
14
+ pagesDir?: string;
15
+ /**
16
+ * The entry point for your server application code.
17
+ * This is where you can define custom server logic, such as API routes or middleware.
18
+ * The build will produce dist/server/index.mjs, which is the entry point for your server application.
19
+ * @default 'server/index'
20
+ */
21
+ serverEntry?: string;
22
+ /**
23
+ * The prefix for your API routes.
24
+ * @default '/api'
25
+ */
26
+ apiPrefix?: string;
12
27
  }): Plugin;
13
28
  //#endregion
14
29
  export { routerPlugin as default };
package/dist/vite.mjs CHANGED
@@ -4,6 +4,11 @@ import { Readable } from "node:stream";
4
4
  import { pipeline } from "node:stream/promises";
5
5
  import { loadEnv } from "vite";
6
6
  //#region src/utils/generateRoutes.ts
7
+ const pageExtensions = [".ts", ".js"];
8
+ const pageExtensionsX = [".tsx", ".jsx"];
9
+ function findFile(files, basename, extensions) {
10
+ return files.find((file) => extensions.some((ext) => file === `${basename}${ext}`));
11
+ }
7
12
  function generateRoutes(viteRoot, pagesDir) {
8
13
  const pagesAbsPath = path.resolve(viteRoot, pagesDir);
9
14
  if (!fs.existsSync(pagesAbsPath)) return { routes: [] };
@@ -12,15 +17,20 @@ function generateRoutes(viteRoot, pagesDir) {
12
17
  function walk(dir, routePath, parentLayout, parentHead) {
13
18
  const files = fs.readdirSync(dir);
14
19
  const importPath = path.relative(viteRoot, dir).replaceAll("\\", "/");
15
- const currentLayout = files.includes("+Layout.tsx") ? `${importPath}/+Layout.tsx` : parentLayout;
16
- const currentHead = files.includes("+Head.tsx") ? `${importPath}/+Head.tsx` : parentHead;
17
- if (files.includes("+Page.tsx")) {
20
+ const layoutFile = findFile(files, "+Layout", pageExtensionsX);
21
+ const currentLayout = layoutFile ? `${importPath}/${layoutFile}` : parentLayout;
22
+ const headFile = findFile(files, "+Head", pageExtensionsX);
23
+ const currentHead = headFile ? `${importPath}/${headFile}` : parentHead;
24
+ const pageFile = findFile(files, "+Page", pageExtensionsX);
25
+ if (pageFile) {
18
26
  const route = {
19
27
  path: routePath || "/",
20
- page: `${importPath}/+Page.tsx`,
21
- hasData: files.includes("+data.ts"),
22
- hasTitle: files.includes("+title.ts")
28
+ page: `${importPath}/${pageFile}`
23
29
  };
30
+ const dataFile = findFile(files, "+data", pageExtensions);
31
+ if (dataFile) route.data = `${importPath}/${dataFile}`;
32
+ const titleFile = findFile(files, "+title", pageExtensions);
33
+ if (titleFile) route.title = `${importPath}/${titleFile}`;
24
34
  if (currentLayout) route.layout = currentLayout;
25
35
  if (currentHead) route.head = currentHead;
26
36
  routes.push(route);
@@ -29,9 +39,10 @@ function generateRoutes(viteRoot, pagesDir) {
29
39
  const fullPath = path.join(dir, file);
30
40
  if (!fs.statSync(fullPath).isDirectory()) continue;
31
41
  if (file === "_error") {
32
- if (fs.readdirSync(fullPath).includes("+Page.tsx")) errorRoute = {
42
+ const errorPageFile = findFile(fs.readdirSync(fullPath), "+Page", pageExtensionsX);
43
+ if (errorPageFile) errorRoute = {
33
44
  path: "_error",
34
- page: `${importPath}/_error/+Page.tsx`,
45
+ page: `${importPath}/_error/${errorPageFile}`,
35
46
  layout: currentLayout,
36
47
  head: currentHead
37
48
  };
@@ -172,10 +183,11 @@ function routerPlugin({ pagesDir = "pages", serverEntry = "server/index", apiPre
172
183
  code += `{path:'${r.path}',page:'${r.page}',Page:()=>import('/${r.page}'),`;
173
184
  if (r.head) code += `head:'${r.head}',Head:()=>import('/${r.head}'),`;
174
185
  if (r.layout) code += `layout:'${r.layout}',Layout:()=>import('/${r.layout}'),`;
175
- code += `hasData:${r.hasData},hasTitle:${r.hasTitle},`;
186
+ if (r.data) code += `data:'${r.data}',`;
187
+ if (r.title) code += `title:'${r.title}',`;
176
188
  if (isSSR) {
177
- if (r.hasData) code += `data:()=>import('/${r.page.replace("+Page.tsx", "+data.ts")}'),`;
178
- if (r.hasTitle) code += `title:()=>import('/${r.page.replace("+Page.tsx", "+title.ts")}'),`;
189
+ if (r.data) code += `Data:()=>import('/${r.data}'),`;
190
+ if (r.title) code += `Title:()=>import('/${r.title}'),`;
179
191
  }
180
192
  code += "},";
181
193
  }
@@ -206,16 +218,15 @@ function routerPlugin({ pagesDir = "pages", serverEntry = "server/index", apiPre
206
218
  }
207
219
  setVikeState({ routes, errorRoute, config, manifest });`;
208
220
  if (id === resolvedVirtualEntryServerId) {
209
- const extensions = [
210
- ".ts",
211
- ".js",
212
- ".mjs"
213
- ];
214
221
  let hasCustomServer = false;
215
222
  let customServerPath = "";
216
223
  if (serverEntry) {
217
224
  const basePath = path.resolve(viteConfigRoot, serverEntry);
218
- for (const ext of ["", ...extensions]) if (fs.existsSync(basePath + ext)) {
225
+ for (const ext of [
226
+ ".ts",
227
+ ".js",
228
+ ".mjs"
229
+ ]) if (fs.existsSync(basePath + ext)) {
219
230
  hasCustomServer = true;
220
231
  customServerPath = (basePath + ext).replaceAll("\\", "/");
221
232
  break;
@@ -241,11 +252,11 @@ if (process.env.NODE_ENV === 'production') {
241
252
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
242
253
  const clientDir = path.resolve(__dirname, '../client');
243
254
  const MIME_TYPES = {
244
- '.html': 'text/html',
245
- '.js': 'text/javascript',
246
- '.mjs': 'text/javascript',
247
- '.css': 'text/css',
248
- '.json': 'application/json',
255
+ '.html': 'text/html; charset=utf-8',
256
+ '.js': 'text/javascript; charset=utf-8',
257
+ '.mjs': 'text/javascript; charset=utf-8',
258
+ '.css': 'text/css; charset=utf-8',
259
+ '.json': 'application/json; charset=utf-8',
249
260
  '.png': 'image/png',
250
261
  '.jpg': 'image/jpeg',
251
262
  '.jpeg': 'image/jpeg',
@@ -254,23 +265,30 @@ if (process.env.NODE_ENV === 'production') {
254
265
  '.ico': 'image/x-icon',
255
266
  '.webmanifest': 'application/manifest+json',
256
267
  '.xml': 'application/xml',
257
- '.txt': 'text/plain',
268
+ '.txt': 'text/plain; charset=utf-8',
258
269
  '.woff': 'font/woff',
259
- '.woff2': 'font/woff2'
270
+ '.woff2': 'font/woff2',
271
+ '.wasm': 'application/wasm'
260
272
  };
273
+ const base = import.meta.env.BASE_URL;
261
274
  const server = createServer(async (req, res) => {
262
275
  try {
263
276
  const urlObj = new URL(req.url || '/', 'http://' + (req.headers.host || 'localhost'));
264
277
  const pathname = urlObj.pathname;
265
- if (pathname !== '/') {
266
- const filePath = path.join(clientDir, pathname);
278
+ let fileUrl = pathname;
279
+ if (base !== '/' && pathname.startsWith(base)) fileUrl = '/' + pathname.slice(base.length);
280
+ if (fileUrl !== '/') {
281
+ const filePath = path.resolve(clientDir, '.' + fileUrl)
282
+ if (!filePath.startsWith(clientDir + path.sep)) {
283
+ res.statusCode = 403
284
+ res.end('Forbidden')
285
+ return
286
+ }
267
287
  if (filePath.startsWith(clientDir) && fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
268
288
  const ext = path.extname(filePath).toLowerCase();
269
289
  const mimeType = MIME_TYPES[ext] || 'application/octet-stream';
270
290
  res.setHeader('Content-Type', mimeType);
271
- if (pathname.startsWith('/assets/')) {
272
- res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
273
- }
291
+ res.setHeader('Cache-Control', fileUrl.startsWith('/assets/') ? 'public, max-age=31536000, immutable' : 'public, max-age=0, must-revalidate')
274
292
  fs.createReadStream(filePath).pipe(res);
275
293
  return;
276
294
  }
@@ -282,6 +300,10 @@ if (process.env.NODE_ENV === 'production') {
282
300
  }
283
301
  const { method } = req;
284
302
  const init = { method, headers };
303
+ if (req.method === 'HEAD') {
304
+ res.end()
305
+ return
306
+ }
285
307
  if (method !== 'GET' && method !== 'HEAD') {
286
308
  init.body = Readable.toWeb(req);
287
309
  init.duplex = 'half';
@@ -292,11 +314,8 @@ if (process.env.NODE_ENV === 'production') {
292
314
  for (const [key, val] of response.headers) {
293
315
  res.setHeader(key, val);
294
316
  }
295
- if (response.body) {
296
- await pipeline(Readable.fromWeb(response.body), res);
297
- } else {
298
- res.end();
299
- }
317
+ if (response.body) await pipeline(Readable.fromWeb(response.body), res);
318
+ else res.end();
300
319
  } catch (e) {
301
320
  console.error(e);
302
321
  res.statusCode = 500;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vike-lite",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "module": "./dist/index.mjs",