vike-lite 1.5.0 → 1.6.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/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) {
@@ -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,22 @@ 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) {
26
+ const dataFile = findFile(files, "+data", pageExtensions);
27
+ const titleFile = findFile(files, "+title", pageExtensions);
18
28
  const route = {
19
29
  path: routePath || "/",
20
- page: `${importPath}/+Page.tsx`,
21
- hasData: files.includes("+data.ts"),
22
- hasTitle: files.includes("+title.ts")
30
+ page: `${importPath}/${pageFile}`,
31
+ hasData: dataFile !== void 0,
32
+ hasTitle: titleFile !== void 0
23
33
  };
34
+ if (dataFile) route.data = `${importPath}/${dataFile}`;
35
+ if (titleFile) route.title = `${importPath}/${titleFile}`;
24
36
  if (currentLayout) route.layout = currentLayout;
25
37
  if (currentHead) route.head = currentHead;
26
38
  routes.push(route);
@@ -29,9 +41,10 @@ function generateRoutes(viteRoot, pagesDir) {
29
41
  const fullPath = path.join(dir, file);
30
42
  if (!fs.statSync(fullPath).isDirectory()) continue;
31
43
  if (file === "_error") {
32
- if (fs.readdirSync(fullPath).includes("+Page.tsx")) errorRoute = {
44
+ const errorPageFile = findFile(fs.readdirSync(fullPath), "+Page", pageExtensionsX);
45
+ if (errorPageFile) errorRoute = {
33
46
  path: "_error",
34
- page: `${importPath}/_error/+Page.tsx`,
47
+ page: `${importPath}/_error/${errorPageFile}`,
35
48
  layout: currentLayout,
36
49
  head: currentHead
37
50
  };
@@ -174,8 +187,8 @@ function routerPlugin({ pagesDir = "pages", serverEntry = "server/index", apiPre
174
187
  if (r.layout) code += `layout:'${r.layout}',Layout:()=>import('/${r.layout}'),`;
175
188
  code += `hasData:${r.hasData},hasTitle:${r.hasTitle},`;
176
189
  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")}'),`;
190
+ if (r.hasData) code += `data:()=>import('/${r.data}'),`;
191
+ if (r.hasTitle) code += `title:()=>import('/${r.title}'),`;
179
192
  }
180
193
  code += "},";
181
194
  }
@@ -206,16 +219,15 @@ function routerPlugin({ pagesDir = "pages", serverEntry = "server/index", apiPre
206
219
  }
207
220
  setVikeState({ routes, errorRoute, config, manifest });`;
208
221
  if (id === resolvedVirtualEntryServerId) {
209
- const extensions = [
210
- ".ts",
211
- ".js",
212
- ".mjs"
213
- ];
214
222
  let hasCustomServer = false;
215
223
  let customServerPath = "";
216
224
  if (serverEntry) {
217
225
  const basePath = path.resolve(viteConfigRoot, serverEntry);
218
- for (const ext of ["", ...extensions]) if (fs.existsSync(basePath + ext)) {
226
+ for (const ext of [
227
+ ".ts",
228
+ ".js",
229
+ ".mjs"
230
+ ]) if (fs.existsSync(basePath + ext)) {
219
231
  hasCustomServer = true;
220
232
  customServerPath = (basePath + ext).replaceAll("\\", "/");
221
233
  break;
@@ -241,11 +253,11 @@ if (process.env.NODE_ENV === 'production') {
241
253
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
242
254
  const clientDir = path.resolve(__dirname, '../client');
243
255
  const MIME_TYPES = {
244
- '.html': 'text/html',
245
- '.js': 'text/javascript',
246
- '.mjs': 'text/javascript',
247
- '.css': 'text/css',
248
- '.json': 'application/json',
256
+ '.html': 'text/html; charset=utf-8',
257
+ '.js': 'text/javascript; charset=utf-8',
258
+ '.mjs': 'text/javascript; charset=utf-8',
259
+ '.css': 'text/css; charset=utf-8',
260
+ '.json': 'application/json; charset=utf-8',
249
261
  '.png': 'image/png',
250
262
  '.jpg': 'image/jpeg',
251
263
  '.jpeg': 'image/jpeg',
@@ -254,23 +266,30 @@ if (process.env.NODE_ENV === 'production') {
254
266
  '.ico': 'image/x-icon',
255
267
  '.webmanifest': 'application/manifest+json',
256
268
  '.xml': 'application/xml',
257
- '.txt': 'text/plain',
269
+ '.txt': 'text/plain; charset=utf-8',
258
270
  '.woff': 'font/woff',
259
- '.woff2': 'font/woff2'
271
+ '.woff2': 'font/woff2',
272
+ '.wasm': 'application/wasm'
260
273
  };
274
+ const base = import.meta.env.BASE_URL;
261
275
  const server = createServer(async (req, res) => {
262
276
  try {
263
277
  const urlObj = new URL(req.url || '/', 'http://' + (req.headers.host || 'localhost'));
264
278
  const pathname = urlObj.pathname;
265
- if (pathname !== '/') {
266
- const filePath = path.join(clientDir, pathname);
279
+ let fileUrl = pathname;
280
+ if (base !== '/' && pathname.startsWith(base)) fileUrl = '/' + pathname.slice(base.length);
281
+ if (fileUrl !== '/') {
282
+ const filePath = path.resolve(clientDir, '.' + fileUrl)
283
+ if (!filePath.startsWith(clientDir + path.sep)) {
284
+ res.statusCode = 403
285
+ res.end('Forbidden')
286
+ return
287
+ }
267
288
  if (filePath.startsWith(clientDir) && fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
268
289
  const ext = path.extname(filePath).toLowerCase();
269
290
  const mimeType = MIME_TYPES[ext] || 'application/octet-stream';
270
291
  res.setHeader('Content-Type', mimeType);
271
- if (pathname.startsWith('/assets/')) {
272
- res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
273
- }
292
+ res.setHeader('Cache-Control', fileUrl.startsWith('/assets/') ? 'public, max-age=31536000, immutable' : 'public, max-age=0, must-revalidate')
274
293
  fs.createReadStream(filePath).pipe(res);
275
294
  return;
276
295
  }
@@ -282,6 +301,10 @@ if (process.env.NODE_ENV === 'production') {
282
301
  }
283
302
  const { method } = req;
284
303
  const init = { method, headers };
304
+ if (req.method === 'HEAD') {
305
+ res.end()
306
+ return
307
+ }
285
308
  if (method !== 'GET' && method !== 'HEAD') {
286
309
  init.body = Readable.toWeb(req);
287
310
  init.duplex = 'half';
@@ -292,11 +315,8 @@ if (process.env.NODE_ENV === 'production') {
292
315
  for (const [key, val] of response.headers) {
293
316
  res.setHeader(key, val);
294
317
  }
295
- if (response.body) {
296
- await pipeline(Readable.fromWeb(response.body), res);
297
- } else {
298
- res.end();
299
- }
318
+ if (response.body) await pipeline(Readable.fromWeb(response.body), res);
319
+ else res.end();
300
320
  } catch (e) {
301
321
  console.error(e);
302
322
  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.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "module": "./dist/index.mjs",