zuby 1.0.38 → 1.0.40

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/branding.js CHANGED
@@ -5,5 +5,5 @@ export const getBrand = () => {
5
5
  return name.charAt(0).toUpperCase() + name.slice(1) + '.js';
6
6
  };
7
7
  export const getTitle = (title) => {
8
- return `${chalk.bgYellow.bold.whiteBright(` ${getBrand()} `)}${chalk.yellowBright.bgWhite(` v${version} `)} ${title}`;
8
+ return `${chalk.bgYellow.bold.whiteBright(` ${getBrand()} `)}${chalk.yellowBright.bgBlackBright(` v${version} `)} ${title}`;
9
9
  };
package/config.d.ts CHANGED
@@ -30,4 +30,4 @@ export declare const mergeDefaultConfig: (config: ZubyConfig) => Promise<ZubyInt
30
30
  * Check which framework components are relying on each plugin
31
31
  * before removing any of them.
32
32
  */
33
- export declare const getBuiltInPlugins: () => import("vite").PluginOption[];
33
+ export declare const getBuiltInPlugins: () => (false | import("vite").Plugin<any> | import("vite").PluginOption[] | Promise<false | import("vite").Plugin<any> | import("vite").PluginOption[] | null | undefined> | null | undefined)[];
package/config.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ZUBY_CONFIG_FILE } from './constants.js';
1
+ import { ZUBY_BUILD_CHUNKS_MANIFEST, ZUBY_CONFIG_FILE } from './constants.js';
2
2
  import { existsSync } from 'fs';
3
3
  import { bundleRequire } from 'bundle-require';
4
4
  import { createLogger } from './logger/index.js';
@@ -7,6 +7,7 @@ import contextPlugin from './plugins/contextPlugin/index.js';
7
7
  import compileTimePlugin from './plugins/compileTimePlugin/index.js';
8
8
  import chunkNamingPlugin from './plugins/chunkNamingPlugin/index.js';
9
9
  import prerenderPlugin from './plugins/prerenderPlugin/index.js';
10
+ import manifestPlugin from './plugins/manifestPlugin/index.js';
10
11
  let zubyInternalConfig;
11
12
  /**
12
13
  * Returns the path to the ZubyConfig file.
@@ -111,8 +112,9 @@ export const mergeDefaultConfig = async (config) => {
111
112
  config.vite.customLogger = config.vite.customLogger ?? config.customLogger;
112
113
  config.vite.server = config.vite.server ?? config.server;
113
114
  config.vite.publicDir = config.vite.publicDir ?? config.publicDir;
114
- config.vite.build.ssrManifest = config.vite.build.ssrManifest ?? 'chunks-manifest.json';
115
+ config.vite.build.ssrManifest = ZUBY_BUILD_CHUNKS_MANIFEST;
115
116
  config.vite.build.ssrEmitAssets = config.vite.build.ssrEmitAssets ?? true;
117
+ config.vite.build.modulePreload = { polyfill: false };
116
118
  config.vite.optimizeDeps = config.vite.optimizeDeps ?? {};
117
119
  // Merge built-in plugins with user plugins
118
120
  config.vite.plugins = [
@@ -131,5 +133,5 @@ export const mergeDefaultConfig = async (config) => {
131
133
  * before removing any of them.
132
134
  */
133
135
  export const getBuiltInPlugins = () => {
134
- return [contextPlugin(), compileTimePlugin(), chunkNamingPlugin(), prerenderPlugin()];
136
+ return [contextPlugin(), compileTimePlugin(), chunkNamingPlugin(), manifestPlugin(), prerenderPlugin()];
135
137
  };
package/constants.d.ts CHANGED
@@ -1 +1,5 @@
1
1
  export declare const ZUBY_CONFIG_FILE = "zuby.config.mjs";
2
+ export declare const ZUBY_BUILD_CHUNKS_MANIFEST = "chunks-manifest.json";
3
+ export declare const ZUBY_CLIENT_CHUNKS_MANIFEST = "client-chunks-manifest.json";
4
+ export declare const ZUBY_SERVER_CHUNKS_MANIFEST = "server-chunks-manifest.json";
5
+ export declare const ZUBY_PAGES_MANIFEST = "pages-manifest.json";
package/constants.js CHANGED
@@ -1 +1,5 @@
1
1
  export const ZUBY_CONFIG_FILE = 'zuby.config.mjs';
2
+ export const ZUBY_BUILD_CHUNKS_MANIFEST = 'chunks-manifest.json';
3
+ export const ZUBY_CLIENT_CHUNKS_MANIFEST = 'client-chunks-manifest.json';
4
+ export const ZUBY_SERVER_CHUNKS_MANIFEST = 'server-chunks-manifest.json';
5
+ export const ZUBY_PAGES_MANIFEST = 'pages-manifest.json';
package/logger/index.js CHANGED
@@ -37,16 +37,13 @@ export function createLogger(level = 'info', options = {}) {
37
37
  if (thresh >= LogLevels[type]) {
38
38
  const method = type === 'info' ? 'log' : type;
39
39
  const format = () => {
40
- const tag = type === 'info'
41
- ? chalk.cyan(chalk.bold(prefix))
42
- : type === 'warn'
43
- ? chalk.yellow(chalk.bold(prefix))
44
- : chalk.red(chalk.bold(prefix));
40
+ const tag = chalk.bold(prefix);
41
+ const fmsg = type === 'warn' ? chalk.yellow(msg) : type === 'error' ? chalk.red(msg) : msg;
45
42
  if (options.timestamp) {
46
- return `${chalk.dim(timeFormatter.format(new Date()))} ${tag} ${msg}`;
43
+ return `${chalk.dim(timeFormatter.format(new Date()))} ${tag} ${fmsg}`;
47
44
  }
48
45
  else {
49
- return `${msg}`;
46
+ return `${fmsg}`;
50
47
  }
51
48
  };
52
49
  if (options.error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuby",
3
- "version": "1.0.38",
3
+ "version": "1.0.40",
4
4
  "description": "Zuby.js is framework for building SPA apps using Vite",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -17,21 +17,20 @@
17
17
  "linkDirectory": true
18
18
  },
19
19
  "dependencies": {
20
- "@vercel/nft": "^0.24.3",
20
+ "@vercel/nft": "^0.26.2",
21
21
  "bundle-require": "^4.0.2",
22
22
  "chalk": "^5.3.0",
23
23
  "commander": "^11.0.0",
24
24
  "devalue": "^4.3.2",
25
- "esbuild": "^0.19.5",
25
+ "esbuild": "^0.19.11",
26
26
  "glob": "^10.3.10",
27
- "inquirer": "^9.2.11",
28
- "jest": "^29.7.0",
27
+ "inquirer": "^9.2.12",
29
28
  "magic-string": "^0.30.5",
30
- "rollup": "^4.0.2",
31
- "vite": "^4.4.11"
29
+ "rollup": "^4.9.5",
30
+ "vite": "^5.0.11"
32
31
  },
33
32
  "devDependencies": {
34
- "@types/inquirer": "^9.0.6"
33
+ "@types/inquirer": "^9.0.7"
35
34
  },
36
35
  "bin": {
37
36
  "zuby": "./commands/index.js"
@@ -0,0 +1,6 @@
1
+ import { Plugin } from 'vite';
2
+ /**
3
+ * This is internal plugin
4
+ * which generated and moves required manifest files.
5
+ */
6
+ export default function manifestPlugin(): Plugin;
@@ -0,0 +1,28 @@
1
+ import { renameSync } from 'fs';
2
+ import { normalizePath } from '../../utils/pathUtils.js';
3
+ import { join } from 'path';
4
+ import { ZUBY_BUILD_CHUNKS_MANIFEST, ZUBY_CLIENT_CHUNKS_MANIFEST, ZUBY_SERVER_CHUNKS_MANIFEST, } from '../../constants.js';
5
+ /**
6
+ * This is internal plugin
7
+ * which generated and moves required manifest files.
8
+ */
9
+ export default function manifestPlugin() {
10
+ let viteConfig;
11
+ return {
12
+ name: 'zuby-manifest-plugin',
13
+ apply: 'build',
14
+ enforce: 'post',
15
+ async configResolved(config) {
16
+ viteConfig = config;
17
+ },
18
+ async closeBundle() {
19
+ // If the build is not for SSR, then skip the plugin
20
+ if (!viteConfig?.build.ssr) {
21
+ return;
22
+ }
23
+ // Move chunks manifest files
24
+ renameSync(normalizePath(join(viteConfig.build.outDir, '..', 'client', ZUBY_BUILD_CHUNKS_MANIFEST)), normalizePath(join(viteConfig.build.outDir, '..', ZUBY_CLIENT_CHUNKS_MANIFEST)));
25
+ renameSync(normalizePath(join(viteConfig.build.outDir, '..', 'server', ZUBY_BUILD_CHUNKS_MANIFEST)), normalizePath(join(viteConfig.build.outDir, '..', ZUBY_SERVER_CHUNKS_MANIFEST)));
26
+ }
27
+ };
28
+ }
@@ -1,10 +1,16 @@
1
- import { existsSync, mkdirSync } from 'fs';
2
- import { dirname, join } from 'path';
1
+ import { existsSync, mkdirSync, readFileSync } from 'fs';
2
+ import { dirname, join, relative, resolve } from 'path';
3
3
  import { getZubyInternalConfig } from '../../config.js';
4
4
  import chalk from 'chalk';
5
5
  import { performance } from 'node:perf_hooks';
6
6
  import ZubyRenderer from '../../server/zubyRenderer.js';
7
7
  import { writeFile } from 'fs/promises';
8
+ import { getPages } from '../../templates/index.js';
9
+ import { OUTPUTS } from '../../types.js';
10
+ import { PATH_TYPES } from '../../templates/types.js';
11
+ import { substitutePathParams } from '../../templates/pathUtils.js';
12
+ import { normalizePath } from '../../utils/pathUtils.js';
13
+ import { ZUBY_SERVER_CHUNKS_MANIFEST } from '../../constants.js';
8
14
  /**
9
15
  * This is internal plugin
10
16
  * that pre-renders the pages during the build.
@@ -24,7 +30,60 @@ export default function prerenderPlugin() {
24
30
  return;
25
31
  }
26
32
  const startTime = performance.now();
27
- const { outDir, customLogger, prerenderPaths, site } = await getZubyInternalConfig();
33
+ const { srcDir, outDir, customLogger, prerenderPaths, site, output } = await getZubyInternalConfig();
34
+ const chunksManifestPath = resolve(outDir, ZUBY_SERVER_CHUNKS_MANIFEST);
35
+ const chunksManifest = existsSync(chunksManifestPath)
36
+ ? JSON.parse(readFileSync(chunksManifestPath, 'utf-8'))
37
+ : {};
38
+ const pages = await getPages();
39
+ for (const page of pages) {
40
+ const srcFilename = normalizePath(relative(srcDir, page.filename));
41
+ const outFilename = chunksManifest[srcFilename]?.shift();
42
+ if (!outFilename) {
43
+ customLogger?.warn(`WARNING: Page "${page.path}" couldn't be pre-rendered because it doesn't have corresponding chunk file.`);
44
+ continue;
45
+ }
46
+ const modulePath = normalizePath(resolve(outDir, 'server', outFilename.substring(1)));
47
+ const module = await import(`file:///${modulePath}`);
48
+ const pagePrerenderValue = module?.prerender && typeof module?.prerender === 'function'
49
+ ? await module?.prerender()
50
+ : module?.prerender;
51
+ const globalPrerenderValue = output === OUTPUTS.static;
52
+ const shouldPrerender = pagePrerenderValue ?? globalPrerenderValue;
53
+ if (!shouldPrerender)
54
+ continue;
55
+ if (page.pathType === PATH_TYPES.static) {
56
+ prerenderPaths.push(page.path);
57
+ continue;
58
+ }
59
+ if (!module?.prerenderPaths && pagePrerenderValue) {
60
+ const exampleParam = page.pathParams.pop() || '';
61
+ const exampleFullPath = substitutePathParams(page.path, { [exampleParam]: 'value3' });
62
+ customLogger?.warn(`WARNING: Page "${page.path}" has dynamic path and enabled pre-rendering but doesn't export prerenderPaths property.`);
63
+ customLogger?.warn(`1. If you don't want to pre-render this page, you need to export prerender property with false value.`);
64
+ customLogger?.warn(`Example: export const prerender = false`);
65
+ customLogger?.warn(`2. If you want to pre-render this page, you need to export prerenderPaths property with array of paths.`);
66
+ customLogger?.warn(`Example: export const prerenderPaths = [ { "${exampleParam}" : "value" }, { "${exampleParam}" : "value2" }, "${exampleFullPath}" ]\r\n`);
67
+ }
68
+ if (!module?.prerenderPaths) {
69
+ continue;
70
+ }
71
+ const pagePrerenderPaths = typeof module?.prerenderPaths === 'function'
72
+ ? await module?.prerenderPaths()
73
+ : module?.prerenderPaths;
74
+ pagePrerenderPaths.forEach((params) => {
75
+ // Handle case when path is defined as string
76
+ // Example: /products/123
77
+ if (typeof params === 'string') {
78
+ return prerenderPaths.push(params);
79
+ }
80
+ // Handle case when path is defined as object of params
81
+ // Example: { id: 123 } => /products/123
82
+ if (typeof params === 'object') {
83
+ return prerenderPaths.push(substitutePathParams(page.path, params));
84
+ }
85
+ });
86
+ }
28
87
  customLogger?.info(`${chalk.bgYellow.bold.whiteBright(` Step 3/4 `)} ${chalk.gray(`pre-rendering pages...`)}`);
29
88
  const zubyRenderer = new ZubyRenderer(outDir);
30
89
  await zubyRenderer.init();
@@ -42,7 +101,9 @@ export default function prerenderPlugin() {
42
101
  zubyRenderer.render(propsReq),
43
102
  ]);
44
103
  const [page, props] = await Promise.all([pageRes.text(), propsRes.text()]);
45
- const pageTarget = join(outDir, 'client', path, 'index.html');
104
+ const pageContentType = pageRes.headers?.get('content-type');
105
+ const pageExtension = pageContentType?.startsWith('application/json') ? 'json' : 'html';
106
+ const pageTarget = join(outDir, 'client', path, `index.${pageExtension}`);
46
107
  const propsTarget = join(outDir, 'client', '_props', path, 'index.json');
47
108
  const pageDir = dirname(pageTarget);
48
109
  const propsDir = dirname(propsTarget);
@@ -54,7 +115,7 @@ export default function prerenderPlugin() {
54
115
  });
55
116
  });
56
117
  await Promise.all([writeFile(pageTarget, page), writeFile(propsTarget, props)]);
57
- console.log('[prerender] Prerendered page: ' + path);
118
+ customLogger.info('[prerender] Pre-rendered path: ' + path);
58
119
  }
59
120
  const finishTime = performance.now();
60
121
  customLogger?.info(chalk.green(`✓ pre-rendered in ${Math.ceil(finishTime - startTime)}ms`));
package/server/index.js CHANGED
@@ -744,7 +744,7 @@ var getBrand = () => {
744
744
  return name.charAt(0).toUpperCase() + name.slice(1) + ".js";
745
745
  };
746
746
  var getTitle = (title) => {
747
- return `${source_default.bgYellow.bold.whiteBright(` ${getBrand()} `)}${source_default.yellowBright.bgWhite(
747
+ return `${source_default.bgYellow.bold.whiteBright(` ${getBrand()} `)}${source_default.yellowBright.bgBlackBright(
748
748
  ` v${version} `
749
749
  )} ${title}`;
750
750
  };
@@ -800,11 +800,12 @@ function createLogger(level = "info", options = {}) {
800
800
  if (thresh >= LogLevels[type]) {
801
801
  const method = type === "info" ? "log" : type;
802
802
  const format = () => {
803
- const tag = type === "info" ? source_default.cyan(source_default.bold(prefix)) : type === "warn" ? source_default.yellow(source_default.bold(prefix)) : source_default.red(source_default.bold(prefix));
803
+ const tag = source_default.bold(prefix);
804
+ const fmsg = type === "warn" ? source_default.yellow(msg) : type === "error" ? source_default.red(msg) : msg;
804
805
  if (options2.timestamp) {
805
- return `${source_default.dim(timeFormatter.format(/* @__PURE__ */ new Date()))} ${tag} ${msg}`;
806
+ return `${source_default.dim(timeFormatter.format(/* @__PURE__ */ new Date()))} ${tag} ${fmsg}`;
806
807
  } else {
807
- return `${msg}`;
808
+ return `${fmsg}`;
808
809
  }
809
810
  };
810
811
  if (options2.error) {
@@ -8388,6 +8389,9 @@ function findMatchingTemplate(templates, path2) {
8388
8389
  };
8389
8390
  }
8390
8391
 
8392
+ // src/constants.ts
8393
+ var ZUBY_CLIENT_CHUNKS_MANIFEST = "client-chunks-manifest.json";
8394
+
8391
8395
  // src/server/zubyRenderer.ts
8392
8396
  var ZubyRenderer = class {
8393
8397
  constructor(outDir) {
@@ -8408,7 +8412,7 @@ var ZubyRenderer = class {
8408
8412
  if (this.entryClientCss) {
8409
8413
  this.entryClientCss = `/chunks/${basename(this.entryClientCss || "")}`;
8410
8414
  }
8411
- const manifestPath = resolve2(this.outDir, "client", "chunks-manifest.json");
8415
+ const manifestPath = resolve2(this.outDir, ZUBY_CLIENT_CHUNKS_MANIFEST);
8412
8416
  if (existsSync(manifestPath)) {
8413
8417
  this.chunksManifest = JSON.parse(readFileSync2(manifestPath, "utf-8"));
8414
8418
  }
@@ -8432,7 +8436,26 @@ var ZubyRenderer = class {
8432
8436
  const handlerFunction = handlerModule.default || handlerModule;
8433
8437
  return handlerFunction(pageContext, next);
8434
8438
  };
8435
- return next();
8439
+ const handlerResult = await next();
8440
+ if (!handlerResult)
8441
+ return;
8442
+ if (handlerResult instanceof Response) {
8443
+ return handlerResult;
8444
+ }
8445
+ if (typeof handlerResult === "string") {
8446
+ return new Response(handlerResult.toString(), {
8447
+ status: pageContext.statusCode || 200,
8448
+ headers: {
8449
+ "Content-Type": "text/html;charset=UTF-8"
8450
+ }
8451
+ });
8452
+ }
8453
+ return new Response(JSON.stringify(handlerResult, null, 2), {
8454
+ status: pageContext.statusCode || 200,
8455
+ headers: {
8456
+ "Content-Type": "application/json;charset=UTF-8"
8457
+ }
8458
+ });
8436
8459
  }
8437
8460
  async executeProps(pageContext) {
8438
8461
  const propsHeader = pageContext.request?.headers.get("x-zuby-props");
@@ -89,8 +89,9 @@ export default class ZubyDevServer extends ZubyServer {
89
89
  cacheDir: normalizePath(join(this.zubyInternalConfig.cacheDir, 'server')),
90
90
  mode: MODES.development,
91
91
  });
92
+ // TODO: Remove this middleware when testing is done
93
+ // this.useBefore(this.viteServerDevServer.middlewares);
92
94
  this.useBefore(this.viteClientDevServer.middlewares);
93
- this.useBefore(this.viteServerDevServer.middlewares);
94
95
  this.renderer = new ZubyDevRenderer(this.zubyInternalConfig, this.viteServerDevServer);
95
96
  watch(join(this.zubyInternalConfig.srcDir, 'pages'), { recursive: true }, (eventType, fileName) => {
96
97
  if (eventType === 'rename')
@@ -5,6 +5,7 @@ import { basename, resolve } from 'path';
5
5
  import { glob } from 'glob';
6
6
  import { existsSync, readFileSync } from 'fs';
7
7
  import { findMatchingTemplate } from '../templates/pathUtils.js';
8
+ import { ZUBY_CLIENT_CHUNKS_MANIFEST } from '../constants.js';
8
9
  export default class ZubyRenderer {
9
10
  constructor(outDir) {
10
11
  this.outDir = outDir;
@@ -24,7 +25,7 @@ export default class ZubyRenderer {
24
25
  if (this.entryClientCss) {
25
26
  this.entryClientCss = `/chunks/${basename(this.entryClientCss || '')}`;
26
27
  }
27
- const manifestPath = resolve(this.outDir, 'client', 'chunks-manifest.json');
28
+ const manifestPath = resolve(this.outDir, ZUBY_CLIENT_CHUNKS_MANIFEST);
28
29
  if (existsSync(manifestPath)) {
29
30
  this.chunksManifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
30
31
  }
@@ -49,7 +50,32 @@ export default class ZubyRenderer {
49
50
  // Execute the handler function
50
51
  return handlerFunction(pageContext, next);
51
52
  };
52
- return next();
53
+ // Execute the handlers chain
54
+ const handlerResult = await next();
55
+ // If handler returned nothing, we'll continue to executeProps and executeEntry
56
+ if (!handlerResult)
57
+ return;
58
+ // If handler returned a response, we can immediately return it
59
+ if (handlerResult instanceof Response) {
60
+ return handlerResult;
61
+ }
62
+ // If handler returned string,
63
+ // we'll assume that user wants to return html response
64
+ if (typeof handlerResult === 'string') {
65
+ return new Response(handlerResult.toString(), {
66
+ status: pageContext.statusCode || 200,
67
+ headers: {
68
+ 'Content-Type': 'text/html;charset=UTF-8',
69
+ },
70
+ });
71
+ }
72
+ // We'll try to serialize everything else as JSON
73
+ return new Response(JSON.stringify(handlerResult, null, 2), {
74
+ status: pageContext.statusCode || 200,
75
+ headers: {
76
+ 'Content-Type': 'application/json;charset=UTF-8',
77
+ },
78
+ });
53
79
  }
54
80
  async executeProps(pageContext) {
55
81
  const propsHeader = pageContext.request?.headers.get('x-zuby-props');
@@ -37,7 +37,7 @@ export declare function getDefaultTemplate(filename: string, templateType: Templ
37
37
  * The pages are already sorted from the least dynamic path to the most dynamic.
38
38
  * If i18n config is defined, the pages will be localized and duplicated for each locale.
39
39
  */
40
- export declare function getTemplates(): Promise<Template[]>;
40
+ export declare function getTemplates(cache?: boolean): Promise<Template[]>;
41
41
  /**
42
42
  * Calculates the weight of each template
43
43
  * and sorts them from the least dynamic path to the most dynamic.
@@ -4,6 +4,7 @@ import { basename, join, resolve } from 'path';
4
4
  import { glob } from 'glob';
5
5
  import { normalizePath } from '../utils/pathUtils.js';
6
6
  import { pathToRegexp, toPath } from './pathUtils.js';
7
+ let templatesCache;
7
8
  /**
8
9
  * Returns the array of pages with static path.
9
10
  */
@@ -91,7 +92,9 @@ export function getDefaultTemplate(filename, templateType) {
91
92
  * The pages are already sorted from the least dynamic path to the most dynamic.
92
93
  * If i18n config is defined, the pages will be localized and duplicated for each locale.
93
94
  */
94
- export async function getTemplates() {
95
+ export async function getTemplates(cache = true) {
96
+ if (cache && templatesCache)
97
+ return templatesCache;
95
98
  const { srcDir, i18n, templateExtensions } = await getZubyInternalConfig();
96
99
  const pagesDir = normalizePath(join(srcDir, 'pages'));
97
100
  const files = await glob(`${pagesDir}/**/*.{${templateExtensions.join(',')}}`);
@@ -135,7 +138,7 @@ export async function getTemplates() {
135
138
  ...template,
136
139
  ...pathToRegexp(template.path),
137
140
  }));
138
- return sortTemplates(templates);
141
+ return (templatesCache = sortTemplates(templates));
139
142
  }
140
143
  /**
141
144
  * Calculates the weight of each template
@@ -1,3 +1,4 @@
1
+ import { PathParamsType } from './types.js';
1
2
  import { LazyTemplate, Template } from './types.js';
2
3
  /**
3
4
  * Returns the matching template for the given path.
@@ -11,6 +12,11 @@ export declare const pathToRegexp: (pathPattern: string) => {
11
12
  pathParams: string[];
12
13
  pathRegex: RegExp;
13
14
  };
15
+ /**
16
+ * Substitutes the path params in the given path.
17
+ * @example substitutePathParams('/products/:id', { id: 123 }) -> '/products/123'
18
+ */
19
+ export declare const substitutePathParams: (path: string, params: PathParamsType) => string;
14
20
  /**
15
21
  * Transforms the page filename with next.js like syntax
16
22
  * to a valid wouter router syntax.
@@ -44,6 +44,21 @@ export const pathToRegexp = (pathPattern) => {
44
44
  pathRegex: new RegExp('^' + result + '(?:\\/)?$', 'i'),
45
45
  };
46
46
  };
47
+ /**
48
+ * Substitutes the path params in the given path.
49
+ * @example substitutePathParams('/products/:id', { id: 123 }) -> '/products/123'
50
+ */
51
+ export const substitutePathParams = (path, params) => {
52
+ return path.replace(/:(\w+)/g, (matched, key) => {
53
+ const value = params[key];
54
+ if (value === undefined)
55
+ return '';
56
+ if (Array.isArray(value)) {
57
+ return value.join('/');
58
+ }
59
+ return value.toString();
60
+ });
61
+ };
47
62
  /**
48
63
  * Transforms the page filename with next.js like syntax
49
64
  * to a valid wouter router syntax.
@@ -3,6 +3,9 @@ export declare const PATH_TYPES: {
3
3
  dynamic: string;
4
4
  };
5
5
  export type PathType = (typeof PATH_TYPES)[keyof typeof PATH_TYPES];
6
+ export type PathParamsType = {
7
+ [key: string]: string | string[] | number | number[] | boolean | boolean[];
8
+ };
6
9
  export interface Template {
7
10
  path: string;
8
11
  pathRegex: RegExp;
package/types.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { Plugin as VitePlugin } from 'vite';
4
4
  import type { PluginOption as VitePluginOption } from 'vite';
5
5
  import { ZubyLogger } from './logger/types.js';
6
6
  import ReadableStream = NodeJS.ReadableStream;
7
+ import { PathParamsType } from './templates/types.js';
7
8
  export interface ZubyConfig {
8
9
  /**
9
10
  * The JSX provider which will be used to render the pages.
@@ -182,3 +183,21 @@ export interface JsxProvider {
182
183
  }
183
184
  export type RenderToString = (vnode: any) => Promise<string> | string;
184
185
  export type RenderToStream = (vnode: any) => Promise<ReadableStream> | ReadableStream;
186
+ /**
187
+ * Export this property with boolean value from page module
188
+ * to enable/disable the pre-rendering of the page.
189
+ * @example export const prerender = false;
190
+ */
191
+ export type Prerender = boolean | (() => boolean) | (() => Promise<boolean>);
192
+ /**
193
+ * Represents single path/item of the prerenderPaths array.
194
+ */
195
+ export type PrerenderPathsItem = PathParamsType | string;
196
+ /**
197
+ * Export this property from page module to specify the paths to pre-render
198
+ * in case the page has dynamic path.
199
+ * @example export const prerenderPaths = ['/products/1', '/products/2'];
200
+ * @example export const prerenderPaths = [{ id: 1 }, { id: 2 }];
201
+ * @example export const prerenderPaths = [{ id: 1 }, '/products/2'];
202
+ */
203
+ export type PrerenderPaths = PrerenderPathsItem[] | (() => PrerenderPathsItem[]) | (() => Promise<PrerenderPathsItem[]>);