vike-lite 1.16.1 → 1.17.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 CHANGED
@@ -29,13 +29,30 @@ export default {
29
29
  vikeLite({
30
30
  pagesDir: 'pages', // Default: Directory containing your pages
31
31
  apiPrefix: '/api', // Default: Prefix to bypass SSR for API routes
32
- prerender: false // Default: Enable SSG globally
32
+ prerender: false, // Default: Enable SSG globally
33
33
  serverEntry: 'server/index', // Default: unfedined value that allows to use a custom server entry file
34
+ printDependencies: false // Default: Print, per build (client/server), which package.json dependencies ended up in the bundle
34
35
  })
35
36
  ]
36
37
  } satisfies UserConfig
37
38
  ```
38
39
 
40
+ #### printDependencies
41
+
42
+ When set to `true`, at the end of each production build (client and server) the plugin prints the list of `package.json` dependencies (and devDependencies) that were actually bundled or externally imported in that specific build output. This is useful to debug bundle composition/size — e.g. spotting a heavy dependency that unexpectedly ended up in the client bundle.
43
+
44
+ ```sh
45
+
46
+ 📦 [Client bundle] package.json dependencies used (1):
47
+ - solid-js@^1.9.3
48
+
49
+ 📦 [Server bundle] package.json dependencies used (2):
50
+ - solid-js@^1.9.3
51
+ - hono@^4.12.31
52
+ ```
53
+
54
+ It's disabled by default since it only adds diagnostic console output and isn't needed for normal usage.
55
+
39
56
  ### 🖥️ Server Integration
40
57
 
41
58
  #### `renderPage()`
@@ -14,6 +14,7 @@ interface RenderContext {
14
14
  };
15
15
  nonce?: string;
16
16
  }
17
+ declare function escapeRegex(str: string): string;
17
18
  declare function matchRoute(urlPathname: string, routes: typeof import('virtual:vike-lite/routes').routes): {
18
19
  route: any;
19
20
  routeParams: Record<string, string>;
@@ -30,4 +31,4 @@ declare function stripBase(pathname: string): string;
30
31
  */
31
32
  declare function prependBase(pathname: string): string;
32
33
  //#endregion
33
- export { BASE_URL, RenderContext, matchRoute, prependBase, stripBase };
34
+ export { BASE_URL, RenderContext, escapeRegex, matchRoute, prependBase, stripBase };
@@ -62,4 +62,4 @@ function prependBase(pathname) {
62
62
  return BASE_URL + (pathname === "/" ? "" : pathname);
63
63
  }
64
64
  //#endregion
65
- export { BASE_URL, matchRoute, prependBase, stripBase };
65
+ export { BASE_URL, escapeRegex, matchRoute, prependBase, stripBase };
@@ -6,8 +6,8 @@ import fs from 'node:fs'
6
6
  import fsPromises from 'node:fs/promises'
7
7
  import path from 'node:path'
8
8
  import { fileURLToPath } from 'node:url'
9
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
10
- const clientDir = path.resolve(__dirname, '../client')
9
+ const clientDir = path.resolve(import.meta.dirname, '../client')
10
+ const assetsDir = path.join(clientDir, 'assets')
11
11
  const MIME_TYPES = {
12
12
  '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8',
13
13
  '.mjs': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8',
@@ -29,7 +29,8 @@ const server = createServer(async (req, res) => {
29
29
  const { BASE_URL } = import.meta.env
30
30
  if (BASE_URL !== '/' && pathname.startsWith(BASE_URL)) fileUrl = '/' + pathname.slice(BASE_URL.length)
31
31
  if (fileUrl !== '/') {
32
- const filePath = path.resolve(clientDir, '.' + fileUrl)
32
+ const normalizedUrl = path.normalize(fileUrl)
33
+ const filePath = path.resolve(clientDir, '.' + normalizedUrl)
33
34
  if (!filePath.startsWith(clientDir)) {
34
35
  res.statusCode = 403
35
36
  res.end('Forbidden')
@@ -41,10 +42,7 @@ const server = createServer(async (req, res) => {
41
42
  const ext = path.extname(filePath).toLowerCase()
42
43
  const mimeType = MIME_TYPES[ext] || 'application/octet-stream'
43
44
  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
- )
45
+ res.setHeader('Cache-Control', filePath.startsWith(assetsDir) ? 'public, max-age=31536000, immutable' : 'public, max-age=0, must-revalidate')
48
46
  fs.createReadStream(filePath).pipe(res)
49
47
  return
50
48
  }
@@ -27,19 +27,17 @@ declare function createDepsConfigPlugin({ packageName, optimizeDepsInclude }: {
27
27
  *
28
28
  * This centralizes logic that is otherwise identical across every `vike-lite-*` package.
29
29
  */
30
- declare function createFrameworkAdapterPlugin({ packageName, hydration, wrapServerHydration, streaming }: {
30
+ declare function createFrameworkAdapterPlugin({ packageName, hydration, streaming }: {
31
31
  /** The framework adapter's package name, e.g. 'vike-lite-react'. */
32
32
  packageName: string;
33
33
  /** @default true */
34
34
  hydration?: boolean;
35
- /** @default true */
36
- wrapServerHydration?: boolean;
37
35
  /**
38
36
  * Stream the server-rendered app markup via the framework's Web Streams-based
39
37
  * SSR API instead of buffering it into a single string. Threaded through to
40
38
  * `onRenderHtml` the same way `hydration` is. Ignored by adapters whose
41
39
  * `onRenderHtml` doesn't accept a `streaming` option.
42
- * @default false
40
+ * @default true
43
41
  */
44
42
  streaming?: boolean;
45
43
  }): Plugin;
@@ -35,7 +35,7 @@ function createDepsConfigPlugin({ packageName, optimizeDepsInclude }) {
35
35
  *
36
36
  * This centralizes logic that is otherwise identical across every `vike-lite-*` package.
37
37
  */
38
- function createFrameworkAdapterPlugin({ packageName, hydration = true, wrapServerHydration = true, streaming = false }) {
38
+ function createFrameworkAdapterPlugin({ packageName, hydration = true, streaming = true }) {
39
39
  const virtualClientId = "virtual:vike-lite/client";
40
40
  const virtualServerId = "virtual:vike-lite/server";
41
41
  const resolvedVirtualClientId = "\0virtual:vike-lite/client";
@@ -49,7 +49,7 @@ function createFrameworkAdapterPlugin({ packageName, hydration = true, wrapServe
49
49
  },
50
50
  load(id) {
51
51
  if (id === resolvedVirtualClientId) return `export const onRenderClient=async(options)=>(await import("${packageName}/__internal/client/onRenderClient")).onRenderClient({...options,hydration:${hydration}});`;
52
- if (id === resolvedVirtualServerId) return `import { onRenderHtml as _onRenderHtml } from '${packageName}/__internal/server/onRenderHtml';export const onRenderHtml = (ctx) => _onRenderHtml({ ...ctx, ${wrapServerHydration ? `hydration:${hydration},streaming:${streaming}` : `streaming:${streaming}`} });`;
52
+ if (id === resolvedVirtualServerId) return `import{onRenderHtml as _onRenderHtml}from'${packageName}/__internal/server/onRenderHtml';export const onRenderHtml=(ctx)=>_onRenderHtml({...ctx,hydration:${hydration},streaming:${streaming}});`;
53
53
  }
54
54
  };
55
55
  }
package/dist/vite.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Plugin } from "vite";
2
2
  //#region src/vite/index.d.ts
3
- declare function vikeLite({ pagesDir, apiPrefix, prerender, serverEntry }?: {
3
+ declare function vikeLite({ pagesDir, apiPrefix, prerender, serverEntry, printDependencies }?: {
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.
@@ -26,6 +26,14 @@ declare function vikeLite({ pagesDir, apiPrefix, prerender, serverEntry }?: {
26
26
  * @default undefined
27
27
  */
28
28
  serverEntry?: string | false;
29
+ /**
30
+ * Whether to print, at the end of each build (client and server), the list of
31
+ * package.json dependencies that ended up in that bundle. Useful to debug bundle
32
+ * size/composition, but adds console output on every production build, so it's
33
+ * disabled by default.
34
+ * @default false
35
+ */
36
+ printDependencies?: boolean;
29
37
  }): Plugin;
30
38
  //#endregion
31
39
  export { vikeLite as default };
package/dist/vite.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { escapeRegex } from "./__internal/shared.mjs";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
4
  import { Readable } from "node:stream";
@@ -109,13 +110,36 @@ const SUPPORTED_RENDERERS = [
109
110
  "vue"
110
111
  ];
111
112
  //#endregion
113
+ //#region src/vite/printDependencies.ts
114
+ function findPackageJsonPath(startDir) {
115
+ let dir = startDir;
116
+ while (true) {
117
+ const candidate = path.join(dir, "package.json");
118
+ if (fs.existsSync(candidate)) return candidate;
119
+ const parent = path.dirname(dir);
120
+ if (parent === dir) return null;
121
+ dir = parent;
122
+ }
123
+ }
124
+ function extractPackageNameFromPath(moduleId) {
125
+ const matches = [...moduleId.replaceAll("\\", "/").matchAll(/node_modules\/(@[^/]+\/[^/]+|[^/]+)/g)];
126
+ if (matches.length === 0) return null;
127
+ return matches[matches.length - 1][1];
128
+ }
129
+ function extractPackageNameFromImport(imp) {
130
+ if (imp.startsWith(".") || imp.startsWith("/") || imp.startsWith("\0") || path.isAbsolute(imp) || imp.startsWith("node:")) return null;
131
+ const parts = imp.split("/");
132
+ return imp.startsWith("@") && parts.length > 1 ? `${parts[0]}/${parts[1]}` : parts[0];
133
+ }
134
+ //#endregion
112
135
  //#region src/vite/index.ts
113
- function vikeLite({ pagesDir = "pages", apiPrefix = "/api", prerender = false, serverEntry } = {}) {
136
+ function vikeLite({ pagesDir = "pages", apiPrefix = "/api", prerender = false, serverEntry, printDependencies = false } = {}) {
114
137
  const isProd = process.env.NODE_ENV === "production";
115
138
  let viteConfigRoot;
116
139
  let outDir;
117
140
  let hasAnyPrerender;
118
141
  let baseUrl;
142
+ let projectDependencies = null;
119
143
  const VIRTUAL = {
120
144
  routes: "virtual:vike-lite/routes",
121
145
  manifest: "virtual:vike-lite/client-manifest",
@@ -139,10 +163,24 @@ function vikeLite({ pagesDir = "pages", apiPrefix = "/api", prerender = false, s
139
163
  outDir = config.build?.outDir ?? "dist";
140
164
  const { emptyOutDir, minify = true, cssMinify = true, sourcemap } = config.build || {};
141
165
  viteConfigRoot = config.root ? path.resolve(config.root) : process.cwd();
166
+ if (isProd && printDependencies) {
167
+ const pkgJsonPath = findPackageJsonPath(viteConfigRoot);
168
+ if (pkgJsonPath) {
169
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
170
+ projectDependencies = {
171
+ ...pkgJson.dependencies,
172
+ ...pkgJson.devDependencies,
173
+ ...pkgJson.peerDependencies
174
+ };
175
+ }
176
+ }
142
177
  const { routes } = generateRoutes(viteConfigRoot, pagesDir);
143
178
  hasAnyPrerender = prerender || routes.some((r) => r.prerender);
144
179
  const serverInput = { index: VIRTUAL.entryServer };
145
180
  if (hasAnyPrerender) serverInput.prerender = VIRTUAL.entryPrerender;
181
+ const escapedPagesDir = escapeRegex(pagesDir);
182
+ const pagesDirRegex = new RegExp(String.raw`[\\/]${escapedPagesDir}[\\/]([^\\/]+)[\\/]`);
183
+ const pagesDirTest = new RegExp(String.raw`[\\/]${escapedPagesDir}[\\/]`);
146
184
  return {
147
185
  appType: "custom",
148
186
  ssr: { noExternal: [/^vike-lite(?:$|-)/] },
@@ -182,15 +220,15 @@ function vikeLite({ pagesDir = "pages", apiPrefix = "/api", prerender = false, s
182
220
  },
183
221
  {
184
222
  name(moduleId) {
185
- const match = moduleId.match(new RegExp(String.raw`[\\/]${pagesDir}[\\/]([^\\/]+)[\\/]`));
223
+ const match = moduleId.match(pagesDirRegex);
186
224
  return match ? `page-${match[1]}` : "shared";
187
225
  },
188
- test: new RegExp(String.raw`[\\/]${pagesDir}[\\/]`),
226
+ test: pagesDirTest,
189
227
  priority: 10
190
228
  },
191
229
  {
192
230
  name(moduleId) {
193
- const match = moduleId.match(new RegExp(String.raw`[\\/]${pagesDir}[\\/]([^\\/]+)[\\/]`));
231
+ const match = moduleId.match(pagesDirRegex);
194
232
  return match ? `css-${match[1]}` : "css-shared";
195
233
  },
196
234
  test: /\.css$/,
@@ -294,6 +332,32 @@ function vikeLite({ pagesDir = "pages", apiPrefix = "/api", prerender = false, s
294
332
  }
295
333
  if (id === RESOLVED.entryPrerender) return importSetup + `export{routes}from'${VIRTUAL.routes}';export{renderPage}from'vike-lite/server';`;
296
334
  },
335
+ generateBundle(_options, bundle) {
336
+ if (!printDependencies || !projectDependencies) return;
337
+ const envName = this.environment.name;
338
+ if (envName !== "client" && envName !== "ssr") return;
339
+ const usedPackages = /* @__PURE__ */ new Set();
340
+ for (const file of Object.values(bundle)) {
341
+ if (file.type !== "chunk") continue;
342
+ for (const moduleId of file.moduleIds) {
343
+ const pkgName = extractPackageNameFromPath(moduleId);
344
+ if (pkgName && projectDependencies[pkgName] !== void 0) usedPackages.add(pkgName);
345
+ }
346
+ for (const imp of file.imports) {
347
+ const pkgName = extractPackageNameFromImport(imp);
348
+ if (pkgName && projectDependencies[pkgName] !== void 0) usedPackages.add(pkgName);
349
+ }
350
+ for (const imp of file.dynamicImports) {
351
+ const pkgName = extractPackageNameFromImport(imp);
352
+ if (pkgName && projectDependencies[pkgName] !== void 0) usedPackages.add(pkgName);
353
+ }
354
+ }
355
+ const label = envName === "client" ? "Client" : "Server";
356
+ const sorted = [...usedPackages].sort();
357
+ console.log(`📦 [${label} bundle] package.json dependencies used (${sorted.length}):`);
358
+ if (sorted.length === 0) console.log(` (None)`);
359
+ else for (const dep of sorted) console.log(` - ${dep}@${projectDependencies[dep]}`);
360
+ },
297
361
  closeBundle: {
298
362
  order: "pre",
299
363
  async handler() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vike-lite",
3
- "version": "1.16.1",
3
+ "version": "1.17.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -82,7 +82,7 @@
82
82
  },
83
83
  "devDependencies": {
84
84
  "@types/node": "^26.1.1",
85
- "tsdown": "^0.22.13",
85
+ "tsdown": "^0.22.14",
86
86
  "vite": "^8.1.5",
87
87
  "vitest": "^4.1.10"
88
88
  },