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 +1 -1
- package/config.d.ts +1 -1
- package/config.js +5 -3
- package/constants.d.ts +4 -0
- package/constants.js +4 -0
- package/logger/index.js +4 -7
- package/package.json +7 -8
- package/plugins/manifestPlugin/index.d.ts +6 -0
- package/plugins/manifestPlugin/index.js +28 -0
- package/plugins/prerenderPlugin/index.js +66 -5
- package/server/index.js +29 -6
- package/server/zubyDevServer.js +2 -1
- package/server/zubyRenderer.js +28 -2
- package/templates/index.d.ts +1 -1
- package/templates/index.js +5 -2
- package/templates/pathUtils.d.ts +6 -0
- package/templates/pathUtils.js +15 -0
- package/templates/types.d.ts +3 -0
- package/types.d.ts +19 -0
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.
|
|
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 =
|
|
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 =
|
|
41
|
-
|
|
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} ${
|
|
43
|
+
return `${chalk.dim(timeFormatter.format(new Date()))} ${tag} ${fmsg}`;
|
|
47
44
|
}
|
|
48
45
|
else {
|
|
49
|
-
return `${
|
|
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.
|
|
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.
|
|
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.
|
|
25
|
+
"esbuild": "^0.19.11",
|
|
26
26
|
"glob": "^10.3.10",
|
|
27
|
-
"inquirer": "^9.2.
|
|
28
|
-
"jest": "^29.7.0",
|
|
27
|
+
"inquirer": "^9.2.12",
|
|
29
28
|
"magic-string": "^0.30.5",
|
|
30
|
-
"rollup": "^4.
|
|
31
|
-
"vite": "^
|
|
29
|
+
"rollup": "^4.9.5",
|
|
30
|
+
"vite": "^5.0.11"
|
|
32
31
|
},
|
|
33
32
|
"devDependencies": {
|
|
34
|
-
"@types/inquirer": "^9.0.
|
|
33
|
+
"@types/inquirer": "^9.0.7"
|
|
35
34
|
},
|
|
36
35
|
"bin": {
|
|
37
36
|
"zuby": "./commands/index.js"
|
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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} ${
|
|
806
|
+
return `${source_default.dim(timeFormatter.format(/* @__PURE__ */ new Date()))} ${tag} ${fmsg}`;
|
|
806
807
|
} else {
|
|
807
|
-
return `${
|
|
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,
|
|
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
|
-
|
|
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");
|
package/server/zubyDevServer.js
CHANGED
|
@@ -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')
|
package/server/zubyRenderer.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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');
|
package/templates/index.d.ts
CHANGED
|
@@ -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.
|
package/templates/index.js
CHANGED
|
@@ -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
|
package/templates/pathUtils.d.ts
CHANGED
|
@@ -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.
|
package/templates/pathUtils.js
CHANGED
|
@@ -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.
|
package/templates/types.d.ts
CHANGED
|
@@ -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[]>);
|