zuby 1.0.39 → 1.0.41
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/config.d.ts +1 -1
- package/config.js +11 -3
- package/constants.d.ts +24 -0
- package/constants.js +27 -0
- package/package.json +7 -8
- package/pageContext/index.d.ts +6 -0
- package/pageContext/index.js +16 -2
- package/plugins/manifestPlugin/index.d.ts +6 -0
- package/plugins/manifestPlugin/index.js +28 -0
- package/plugins/prerenderPlugin/index.js +2 -1
- package/server/index.js +127 -37
- package/server/zubyDevRenderer.js +4 -0
- package/server/zubyDevServer.js +0 -1
- package/server/zubyRenderer.d.ts +1 -0
- package/server/zubyRenderer.js +28 -12
- package/server/zubyServer.d.ts +6 -4
- package/server/zubyServer.js +31 -7
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 { 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 = 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,11 @@ export const mergeDefaultConfig = async (config) => {
|
|
|
131
133
|
* before removing any of them.
|
|
132
134
|
*/
|
|
133
135
|
export const getBuiltInPlugins = () => {
|
|
134
|
-
return [
|
|
136
|
+
return [
|
|
137
|
+
contextPlugin(),
|
|
138
|
+
compileTimePlugin(),
|
|
139
|
+
chunkNamingPlugin(),
|
|
140
|
+
manifestPlugin(),
|
|
141
|
+
prerenderPlugin(),
|
|
142
|
+
];
|
|
135
143
|
};
|
package/constants.d.ts
CHANGED
|
@@ -1 +1,25 @@
|
|
|
1
1
|
export declare const ZUBY_CONFIG_FILE = "zuby.config.mjs";
|
|
2
|
+
export declare const BUILD_CHUNKS_MANIFEST = "chunks-manifest.json";
|
|
3
|
+
export declare const CLIENT_CHUNKS_MANIFEST = "client-chunks-manifest.json";
|
|
4
|
+
export declare const SERVER_CHUNKS_MANIFEST = "server-chunks-manifest.json";
|
|
5
|
+
export declare const PAGES_MANIFEST = "pages-manifest.json";
|
|
6
|
+
export declare const HTTP_HEADERS: {
|
|
7
|
+
Age: string;
|
|
8
|
+
Host: string;
|
|
9
|
+
Server: string;
|
|
10
|
+
ContentType: string;
|
|
11
|
+
ContentLength: string;
|
|
12
|
+
ContentRange: string;
|
|
13
|
+
ContentEncoding: string;
|
|
14
|
+
ContentLanguage: string;
|
|
15
|
+
AcceptRanges: string;
|
|
16
|
+
CacheControl: string;
|
|
17
|
+
XForwardedProto: string;
|
|
18
|
+
XForwardedHost: string;
|
|
19
|
+
XPoweredBy: string;
|
|
20
|
+
XZubyVersion: string;
|
|
21
|
+
XZubyProps: string;
|
|
22
|
+
XZubyCachedTime: string;
|
|
23
|
+
XZubyCacheTTL: string;
|
|
24
|
+
XZubyCache: string;
|
|
25
|
+
};
|
package/constants.js
CHANGED
|
@@ -1 +1,28 @@
|
|
|
1
1
|
export const ZUBY_CONFIG_FILE = 'zuby.config.mjs';
|
|
2
|
+
export const BUILD_CHUNKS_MANIFEST = 'chunks-manifest.json';
|
|
3
|
+
export const CLIENT_CHUNKS_MANIFEST = 'client-chunks-manifest.json';
|
|
4
|
+
export const SERVER_CHUNKS_MANIFEST = 'server-chunks-manifest.json';
|
|
5
|
+
export const PAGES_MANIFEST = 'pages-manifest.json';
|
|
6
|
+
export const HTTP_HEADERS = {
|
|
7
|
+
// Standard HTTP headers
|
|
8
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
|
|
9
|
+
Age: 'Age',
|
|
10
|
+
Host: 'Host',
|
|
11
|
+
Server: 'Server',
|
|
12
|
+
ContentType: 'Content-Type',
|
|
13
|
+
ContentLength: 'Content-Length',
|
|
14
|
+
ContentRange: 'Content-Range',
|
|
15
|
+
ContentEncoding: 'Content-Encoding',
|
|
16
|
+
ContentLanguage: 'Content-Language',
|
|
17
|
+
AcceptRanges: 'Accept-Ranges',
|
|
18
|
+
CacheControl: 'Cache-Control',
|
|
19
|
+
XForwardedProto: 'X-Forwarded-Proto',
|
|
20
|
+
XForwardedHost: 'X-Forwarded-Host',
|
|
21
|
+
XPoweredBy: 'X-Powered-By',
|
|
22
|
+
// Zuby specific
|
|
23
|
+
XZubyVersion: 'X-Zuby-Version',
|
|
24
|
+
XZubyProps: 'X-Zuby-Props',
|
|
25
|
+
XZubyCachedTime: 'X-Zuby-Cached-Time',
|
|
26
|
+
XZubyCacheTTL: 'X-Zuby-Cache-TTL',
|
|
27
|
+
XZubyCache: 'X-Zuby-Cache',
|
|
28
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zuby",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.41",
|
|
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"
|
package/pageContext/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export declare class ZubyPageContext {
|
|
|
6
6
|
protected _params: Record<string, string>;
|
|
7
7
|
protected _clientAddress?: string;
|
|
8
8
|
protected _statusCode: number;
|
|
9
|
+
protected _headers: Headers;
|
|
10
|
+
protected _cache: number;
|
|
9
11
|
protected _props: Record<string, any>;
|
|
10
12
|
protected _zubyContext: ZubyContext;
|
|
11
13
|
constructor(options: {
|
|
@@ -15,6 +17,7 @@ export declare class ZubyPageContext {
|
|
|
15
17
|
clientAddress?: string;
|
|
16
18
|
statusCode?: number;
|
|
17
19
|
props?: Record<string, any>;
|
|
20
|
+
headers?: Headers;
|
|
18
21
|
zubyContext?: ZubyContext;
|
|
19
22
|
});
|
|
20
23
|
get url(): URL;
|
|
@@ -40,4 +43,7 @@ export declare class ZubyPageContext {
|
|
|
40
43
|
set props(value: Record<string, any>);
|
|
41
44
|
get statusCode(): number | undefined;
|
|
42
45
|
set statusCode(value: number | undefined);
|
|
46
|
+
get headers(): Headers;
|
|
47
|
+
get cache(): number;
|
|
48
|
+
set cache(value: number);
|
|
43
49
|
}
|
package/pageContext/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { getContext } from '../context/index.js';
|
|
2
2
|
export class ZubyPageContext {
|
|
3
3
|
constructor(options) {
|
|
4
|
-
this._url = options?.url || new URL('http://localhost/');
|
|
5
|
-
this._title = options?.title || '';
|
|
6
4
|
this._request = options?.request;
|
|
5
|
+
this._url = options?.url || new URL(options?.request?.url || '/', 'http://localhost/');
|
|
6
|
+
this._title = options?.title || '';
|
|
7
7
|
this._params = {};
|
|
8
8
|
this._clientAddress = options?.clientAddress;
|
|
9
9
|
this._statusCode = options?.statusCode || 200;
|
|
10
10
|
this._props = options?.props || {};
|
|
11
|
+
this._cache = 0;
|
|
12
|
+
this._headers = options?.headers || new Headers();
|
|
11
13
|
this._zubyContext = options?.zubyContext || getContext();
|
|
12
14
|
}
|
|
13
15
|
get url() {
|
|
@@ -70,4 +72,16 @@ export class ZubyPageContext {
|
|
|
70
72
|
}
|
|
71
73
|
this._statusCode = value;
|
|
72
74
|
}
|
|
75
|
+
get headers() {
|
|
76
|
+
return this._headers;
|
|
77
|
+
}
|
|
78
|
+
get cache() {
|
|
79
|
+
return this._cache;
|
|
80
|
+
}
|
|
81
|
+
set cache(value) {
|
|
82
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
83
|
+
throw new Error(`Invalid cache value: ${value}`);
|
|
84
|
+
}
|
|
85
|
+
this._cache = value;
|
|
86
|
+
}
|
|
73
87
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { renameSync } from 'fs';
|
|
2
|
+
import { normalizePath } from '../../utils/pathUtils.js';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { BUILD_CHUNKS_MANIFEST, CLIENT_CHUNKS_MANIFEST, 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', BUILD_CHUNKS_MANIFEST)), normalizePath(join(viteConfig.build.outDir, '..', CLIENT_CHUNKS_MANIFEST)));
|
|
25
|
+
renameSync(normalizePath(join(viteConfig.build.outDir, '..', 'server', BUILD_CHUNKS_MANIFEST)), normalizePath(join(viteConfig.build.outDir, '..', SERVER_CHUNKS_MANIFEST)));
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -10,6 +10,7 @@ import { OUTPUTS } from '../../types.js';
|
|
|
10
10
|
import { PATH_TYPES } from '../../templates/types.js';
|
|
11
11
|
import { substitutePathParams } from '../../templates/pathUtils.js';
|
|
12
12
|
import { normalizePath } from '../../utils/pathUtils.js';
|
|
13
|
+
import { SERVER_CHUNKS_MANIFEST } from '../../constants.js';
|
|
13
14
|
/**
|
|
14
15
|
* This is internal plugin
|
|
15
16
|
* that pre-renders the pages during the build.
|
|
@@ -30,7 +31,7 @@ export default function prerenderPlugin() {
|
|
|
30
31
|
}
|
|
31
32
|
const startTime = performance.now();
|
|
32
33
|
const { srcDir, outDir, customLogger, prerenderPaths, site, output } = await getZubyInternalConfig();
|
|
33
|
-
const chunksManifestPath = resolve(outDir,
|
|
34
|
+
const chunksManifestPath = resolve(outDir, SERVER_CHUNKS_MANIFEST);
|
|
34
35
|
const chunksManifest = existsSync(chunksManifestPath)
|
|
35
36
|
? JSON.parse(readFileSync(chunksManifestPath, 'utf-8'))
|
|
36
37
|
: {};
|
package/server/index.js
CHANGED
|
@@ -2099,13 +2099,15 @@ var getContext = () => {
|
|
|
2099
2099
|
// src/pageContext/index.ts
|
|
2100
2100
|
var ZubyPageContext = class {
|
|
2101
2101
|
constructor(options) {
|
|
2102
|
-
this._url = options?.url || new URL("http://localhost/");
|
|
2103
|
-
this._title = options?.title || "";
|
|
2104
2102
|
this._request = options?.request;
|
|
2103
|
+
this._url = options?.url || new URL(options?.request?.url || "/", "http://localhost/");
|
|
2104
|
+
this._title = options?.title || "";
|
|
2105
2105
|
this._params = {};
|
|
2106
2106
|
this._clientAddress = options?.clientAddress;
|
|
2107
2107
|
this._statusCode = options?.statusCode || 200;
|
|
2108
2108
|
this._props = options?.props || {};
|
|
2109
|
+
this._cache = 0;
|
|
2110
|
+
this._headers = options?.headers || new Headers();
|
|
2109
2111
|
this._zubyContext = options?.zubyContext || getContext();
|
|
2110
2112
|
}
|
|
2111
2113
|
get url() {
|
|
@@ -2168,6 +2170,18 @@ var ZubyPageContext = class {
|
|
|
2168
2170
|
}
|
|
2169
2171
|
this._statusCode = value;
|
|
2170
2172
|
}
|
|
2173
|
+
get headers() {
|
|
2174
|
+
return this._headers;
|
|
2175
|
+
}
|
|
2176
|
+
get cache() {
|
|
2177
|
+
return this._cache;
|
|
2178
|
+
}
|
|
2179
|
+
set cache(value) {
|
|
2180
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
2181
|
+
throw new Error(`Invalid cache value: ${value}`);
|
|
2182
|
+
}
|
|
2183
|
+
this._cache = value;
|
|
2184
|
+
}
|
|
2171
2185
|
};
|
|
2172
2186
|
|
|
2173
2187
|
// src/server/zubyRenderer.ts
|
|
@@ -8389,6 +8403,32 @@ function findMatchingTemplate(templates, path2) {
|
|
|
8389
8403
|
};
|
|
8390
8404
|
}
|
|
8391
8405
|
|
|
8406
|
+
// src/constants.ts
|
|
8407
|
+
var CLIENT_CHUNKS_MANIFEST = "client-chunks-manifest.json";
|
|
8408
|
+
var HTTP_HEADERS = {
|
|
8409
|
+
// Standard HTTP headers
|
|
8410
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
|
|
8411
|
+
Age: "Age",
|
|
8412
|
+
Host: "Host",
|
|
8413
|
+
Server: "Server",
|
|
8414
|
+
ContentType: "Content-Type",
|
|
8415
|
+
ContentLength: "Content-Length",
|
|
8416
|
+
ContentRange: "Content-Range",
|
|
8417
|
+
ContentEncoding: "Content-Encoding",
|
|
8418
|
+
ContentLanguage: "Content-Language",
|
|
8419
|
+
AcceptRanges: "Accept-Ranges",
|
|
8420
|
+
CacheControl: "Cache-Control",
|
|
8421
|
+
XForwardedProto: "X-Forwarded-Proto",
|
|
8422
|
+
XForwardedHost: "X-Forwarded-Host",
|
|
8423
|
+
XPoweredBy: "X-Powered-By",
|
|
8424
|
+
// Zuby specific
|
|
8425
|
+
XZubyVersion: "X-Zuby-Version",
|
|
8426
|
+
XZubyProps: "X-Zuby-Props",
|
|
8427
|
+
XZubyCachedTime: "X-Zuby-Cached-Time",
|
|
8428
|
+
XZubyCacheTTL: "X-Zuby-Cache-TTL",
|
|
8429
|
+
XZubyCache: "X-Zuby-Cache"
|
|
8430
|
+
};
|
|
8431
|
+
|
|
8392
8432
|
// src/server/zubyRenderer.ts
|
|
8393
8433
|
var ZubyRenderer = class {
|
|
8394
8434
|
constructor(outDir) {
|
|
@@ -8409,7 +8449,7 @@ var ZubyRenderer = class {
|
|
|
8409
8449
|
if (this.entryClientCss) {
|
|
8410
8450
|
this.entryClientCss = `/chunks/${basename(this.entryClientCss || "")}`;
|
|
8411
8451
|
}
|
|
8412
|
-
const manifestPath = resolve2(this.outDir,
|
|
8452
|
+
const manifestPath = resolve2(this.outDir, CLIENT_CHUNKS_MANIFEST);
|
|
8413
8453
|
if (existsSync(manifestPath)) {
|
|
8414
8454
|
this.chunksManifest = JSON.parse(readFileSync2(manifestPath, "utf-8"));
|
|
8415
8455
|
}
|
|
@@ -8437,33 +8477,42 @@ var ZubyRenderer = class {
|
|
|
8437
8477
|
if (!handlerResult)
|
|
8438
8478
|
return;
|
|
8439
8479
|
if (handlerResult instanceof Response) {
|
|
8440
|
-
return handlerResult;
|
|
8480
|
+
return this.injectHeaders(handlerResult, pageContext);
|
|
8441
8481
|
}
|
|
8442
8482
|
if (typeof handlerResult === "string") {
|
|
8443
|
-
return
|
|
8483
|
+
return this.injectHeaders(
|
|
8484
|
+
new Response(handlerResult.toString(), {
|
|
8485
|
+
status: pageContext.statusCode || 200,
|
|
8486
|
+
headers: {
|
|
8487
|
+
"Content-Type": "text/html;charset=UTF-8"
|
|
8488
|
+
}
|
|
8489
|
+
}),
|
|
8490
|
+
pageContext
|
|
8491
|
+
);
|
|
8492
|
+
}
|
|
8493
|
+
return this.injectHeaders(
|
|
8494
|
+
new Response(JSON.stringify(handlerResult, null, 2), {
|
|
8444
8495
|
status: pageContext.statusCode || 200,
|
|
8445
8496
|
headers: {
|
|
8446
|
-
"Content-Type": "
|
|
8497
|
+
"Content-Type": "application/json;charset=UTF-8"
|
|
8447
8498
|
}
|
|
8448
|
-
})
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
status: pageContext.statusCode || 200,
|
|
8452
|
-
headers: {
|
|
8453
|
-
"Content-Type": "application/json;charset=UTF-8"
|
|
8454
|
-
}
|
|
8455
|
-
});
|
|
8499
|
+
}),
|
|
8500
|
+
pageContext
|
|
8501
|
+
);
|
|
8456
8502
|
}
|
|
8457
8503
|
async executeProps(pageContext) {
|
|
8458
|
-
const propsHeader = pageContext.request?.headers.get(
|
|
8504
|
+
const propsHeader = pageContext.request?.headers.get(HTTP_HEADERS.XZubyProps);
|
|
8459
8505
|
if (propsHeader !== "true")
|
|
8460
8506
|
return;
|
|
8461
|
-
return
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8507
|
+
return this.injectHeaders(
|
|
8508
|
+
new Response(JSON.stringify(pageContext.props, null, 2), {
|
|
8509
|
+
status: pageContext.statusCode || 200,
|
|
8510
|
+
headers: {
|
|
8511
|
+
"Content-Type": "application/json"
|
|
8512
|
+
}
|
|
8513
|
+
}),
|
|
8514
|
+
pageContext
|
|
8515
|
+
);
|
|
8467
8516
|
}
|
|
8468
8517
|
async executeEntry(pageContext) {
|
|
8469
8518
|
const pages = this.zubyContext?.templates?.pages || [];
|
|
@@ -8491,7 +8540,8 @@ var ZubyRenderer = class {
|
|
|
8491
8540
|
) || "";
|
|
8492
8541
|
const layoutChildren = layoutComponent({
|
|
8493
8542
|
children: innerLayoutComponent({
|
|
8494
|
-
innerHtml: entryHtml
|
|
8543
|
+
innerHtml: entryHtml,
|
|
8544
|
+
context: pageContext
|
|
8495
8545
|
}),
|
|
8496
8546
|
context: pageContext
|
|
8497
8547
|
});
|
|
@@ -8515,42 +8565,59 @@ var ZubyRenderer = class {
|
|
|
8515
8565
|
jsImports.forEach((imp) => {
|
|
8516
8566
|
html = html.replace(/(<\/head>)/, `<script type="module" src="${imp}" defer></script>$1`);
|
|
8517
8567
|
});
|
|
8518
|
-
return
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8568
|
+
return this.injectHeaders(
|
|
8569
|
+
new Response(html, {
|
|
8570
|
+
status: pageContext.statusCode || 200,
|
|
8571
|
+
headers: {
|
|
8572
|
+
"Content-Type": "text/html;charset=UTF-8"
|
|
8573
|
+
}
|
|
8574
|
+
}),
|
|
8575
|
+
pageContext
|
|
8576
|
+
);
|
|
8524
8577
|
}
|
|
8525
8578
|
createPageContext(req) {
|
|
8526
8579
|
const pageUrl = new URL(req.url);
|
|
8527
8580
|
if (pageUrl.pathname.startsWith("/_props/")) {
|
|
8528
8581
|
pageUrl.pathname = pageUrl.pathname.replace(/^\/_props\//, "/");
|
|
8529
|
-
req.headers.set(
|
|
8582
|
+
req.headers.set(HTTP_HEADERS.XZubyProps, "true");
|
|
8530
8583
|
}
|
|
8531
8584
|
return new ZubyPageContext({
|
|
8532
8585
|
url: pageUrl,
|
|
8533
8586
|
request: req,
|
|
8534
8587
|
statusCode: 200,
|
|
8535
8588
|
props: {},
|
|
8536
|
-
zubyContext: this.zubyContext
|
|
8589
|
+
zubyContext: this.zubyContext,
|
|
8590
|
+
headers: new Headers({
|
|
8591
|
+
[HTTP_HEADERS.XPoweredBy]: "Zuby.js"
|
|
8592
|
+
})
|
|
8537
8593
|
});
|
|
8538
8594
|
}
|
|
8539
8595
|
async render(req) {
|
|
8540
8596
|
const pageContext = this.createPageContext(req);
|
|
8541
8597
|
return await this.executeHandlers(pageContext) || await this.executeProps(pageContext) || await this.executeEntry(pageContext);
|
|
8542
8598
|
}
|
|
8599
|
+
injectHeaders(res, pageContext) {
|
|
8600
|
+
pageContext.headers.forEach((value, key) => {
|
|
8601
|
+
if (res.headers.has(key))
|
|
8602
|
+
return;
|
|
8603
|
+
res.headers.set(key, value);
|
|
8604
|
+
});
|
|
8605
|
+
if (pageContext.cache) {
|
|
8606
|
+
res.headers.set(HTTP_HEADERS.XZubyCacheTTL, pageContext.cache.toString());
|
|
8607
|
+
}
|
|
8608
|
+
return res;
|
|
8609
|
+
}
|
|
8543
8610
|
};
|
|
8544
8611
|
|
|
8545
8612
|
// src/server/zubyServer.ts
|
|
8546
8613
|
var ZubyServer = class {
|
|
8547
8614
|
constructor(options) {
|
|
8615
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
8548
8616
|
this.options = options;
|
|
8549
8617
|
this.options.responseHeaders = this.options.responseHeaders || {};
|
|
8550
8618
|
this.logger = options.logger || createLogger("info");
|
|
8551
8619
|
this.renderer = options.renderer || new ZubyRenderer(options.outDir);
|
|
8552
8620
|
this.middlewares = [
|
|
8553
|
-
this.defaultHeadersMiddleware.bind(this),
|
|
8554
8621
|
this.trailingSlashMiddleware.bind(this),
|
|
8555
8622
|
this.clientDirMiddleware.bind(this),
|
|
8556
8623
|
this.serverDirMiddleware.bind(this)
|
|
@@ -8630,11 +8697,38 @@ var ZubyServer = class {
|
|
|
8630
8697
|
res.end();
|
|
8631
8698
|
});
|
|
8632
8699
|
}
|
|
8633
|
-
async serverDirMiddleware(nodeReq, nodeRes) {
|
|
8700
|
+
async serverDirMiddleware(nodeReq, nodeRes, _next) {
|
|
8634
8701
|
const req = await this.toRequest(nodeReq);
|
|
8635
|
-
const
|
|
8702
|
+
const cacheKey = new URL(req.url, "http://localhost").pathname;
|
|
8703
|
+
const res = this.cacheRead(cacheKey) || this.cacheWrite(cacheKey, await this.renderer.render(req));
|
|
8636
8704
|
return this.toNodeResponse(res, nodeRes);
|
|
8637
8705
|
}
|
|
8706
|
+
cacheRead(key) {
|
|
8707
|
+
const cachedResponse = this.cache.get(key)?.clone();
|
|
8708
|
+
if (!cachedResponse)
|
|
8709
|
+
return void 0;
|
|
8710
|
+
const cachedTime = Number(cachedResponse.headers.get(HTTP_HEADERS.XZubyCachedTime));
|
|
8711
|
+
const cacheTTL = Number(cachedResponse.headers.get(HTTP_HEADERS.XZubyCacheTTL));
|
|
8712
|
+
const age = Math.ceil(((/* @__PURE__ */ new Date()).getTime() - cachedTime) / 1e3);
|
|
8713
|
+
if (age > cacheTTL) {
|
|
8714
|
+
this.cache.delete(key);
|
|
8715
|
+
return void 0;
|
|
8716
|
+
}
|
|
8717
|
+
cachedResponse.headers.set(HTTP_HEADERS.Age, age.toString());
|
|
8718
|
+
cachedResponse.headers.set(HTTP_HEADERS.XZubyCache, "HIT");
|
|
8719
|
+
return cachedResponse;
|
|
8720
|
+
}
|
|
8721
|
+
cacheWrite(key, response) {
|
|
8722
|
+
const cacheTTL = Number(response.headers.get(HTTP_HEADERS.XZubyCacheTTL));
|
|
8723
|
+
if (!cacheTTL) {
|
|
8724
|
+
response.headers.set(HTTP_HEADERS.XZubyCache, "BYPASS");
|
|
8725
|
+
return response;
|
|
8726
|
+
}
|
|
8727
|
+
response.headers.set(HTTP_HEADERS.XZubyCachedTime, (/* @__PURE__ */ new Date()).getTime().toString());
|
|
8728
|
+
response.headers.set(HTTP_HEADERS.XZubyCache, "MISS");
|
|
8729
|
+
this.cache.set(key, response.clone());
|
|
8730
|
+
return response;
|
|
8731
|
+
}
|
|
8638
8732
|
async listen() {
|
|
8639
8733
|
const serverPromise = new Promise((resolve5, reject) => {
|
|
8640
8734
|
this.server.listen(this.options.port, this.options.host, 511, () => {
|
|
@@ -8656,10 +8750,6 @@ var ZubyServer = class {
|
|
|
8656
8750
|
});
|
|
8657
8751
|
});
|
|
8658
8752
|
}
|
|
8659
|
-
defaultHeadersMiddleware(_req, res, next) {
|
|
8660
|
-
res.setHeader("X-Powered-By", "Zuby.js");
|
|
8661
|
-
next();
|
|
8662
|
-
}
|
|
8663
8753
|
trailingSlashMiddleware(req, res, next) {
|
|
8664
8754
|
const [path2, query] = req.url?.split("?") || [];
|
|
8665
8755
|
if (path2.endsWith("/") || path2.match(/[.](.+)$/)) {
|
|
@@ -3,6 +3,7 @@ import { relative } from 'path';
|
|
|
3
3
|
import { getContext } from '../context/index.js';
|
|
4
4
|
import ZubyRenderer from './zubyRenderer.js';
|
|
5
5
|
import { getEntryFile } from '../commands/build.js';
|
|
6
|
+
import { HTTP_HEADERS } from '../constants.js';
|
|
6
7
|
export default class ZubyDevRenderer extends ZubyRenderer {
|
|
7
8
|
constructor(zubyConfig, viteServerDevServer) {
|
|
8
9
|
super(zubyConfig.outDir);
|
|
@@ -23,6 +24,9 @@ export default class ZubyDevRenderer extends ZubyRenderer {
|
|
|
23
24
|
}
|
|
24
25
|
async render(req) {
|
|
25
26
|
const originalRes = await super.render(req);
|
|
27
|
+
// Do not cache responses in dev mode
|
|
28
|
+
originalRes.headers.set(HTTP_HEADERS.XZubyCacheTTL, '0');
|
|
29
|
+
originalRes.headers.set(HTTP_HEADERS.CacheControl, 'no-store, no-cache, must-revalidate');
|
|
26
30
|
// Do not transform non-html responses
|
|
27
31
|
if (!originalRes.headers.get('content-type')?.includes('text/html'))
|
|
28
32
|
return originalRes;
|
package/server/zubyDevServer.js
CHANGED
|
@@ -15,7 +15,6 @@ export default class ZubyDevServer extends ZubyServer {
|
|
|
15
15
|
});
|
|
16
16
|
this.zubyInternalConfig = zubyConfig;
|
|
17
17
|
this.middlewares = [
|
|
18
|
-
this.defaultHeadersMiddleware.bind(this),
|
|
19
18
|
this.trailingSlashMiddleware.bind(this),
|
|
20
19
|
this.serverDirMiddleware.bind(this),
|
|
21
20
|
];
|
package/server/zubyRenderer.d.ts
CHANGED
|
@@ -21,4 +21,5 @@ export default class ZubyRenderer {
|
|
|
21
21
|
executeEntry(pageContext: ZubyPageContext): Promise<Response>;
|
|
22
22
|
createPageContext(req: Request): ZubyPageContext;
|
|
23
23
|
render(req: Request): Promise<Response>;
|
|
24
|
+
injectHeaders(res: Response, pageContext: ZubyPageContext): Response;
|
|
24
25
|
}
|
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 { CLIENT_CHUNKS_MANIFEST, HTTP_HEADERS } 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, CLIENT_CHUNKS_MANIFEST);
|
|
28
29
|
if (existsSync(manifestPath)) {
|
|
29
30
|
this.chunksManifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
30
31
|
}
|
|
@@ -56,36 +57,36 @@ export default class ZubyRenderer {
|
|
|
56
57
|
return;
|
|
57
58
|
// If handler returned a response, we can immediately return it
|
|
58
59
|
if (handlerResult instanceof Response) {
|
|
59
|
-
return handlerResult;
|
|
60
|
+
return this.injectHeaders(handlerResult, pageContext);
|
|
60
61
|
}
|
|
61
62
|
// If handler returned string,
|
|
62
63
|
// we'll assume that user wants to return html response
|
|
63
64
|
if (typeof handlerResult === 'string') {
|
|
64
|
-
return new Response(handlerResult.toString(), {
|
|
65
|
+
return this.injectHeaders(new Response(handlerResult.toString(), {
|
|
65
66
|
status: pageContext.statusCode || 200,
|
|
66
67
|
headers: {
|
|
67
68
|
'Content-Type': 'text/html;charset=UTF-8',
|
|
68
69
|
},
|
|
69
|
-
});
|
|
70
|
+
}), pageContext);
|
|
70
71
|
}
|
|
71
72
|
// We'll try to serialize everything else as JSON
|
|
72
|
-
return new Response(JSON.stringify(handlerResult, null, 2), {
|
|
73
|
+
return this.injectHeaders(new Response(JSON.stringify(handlerResult, null, 2), {
|
|
73
74
|
status: pageContext.statusCode || 200,
|
|
74
75
|
headers: {
|
|
75
76
|
'Content-Type': 'application/json;charset=UTF-8',
|
|
76
77
|
},
|
|
77
|
-
});
|
|
78
|
+
}), pageContext);
|
|
78
79
|
}
|
|
79
80
|
async executeProps(pageContext) {
|
|
80
|
-
const propsHeader = pageContext.request?.headers.get(
|
|
81
|
+
const propsHeader = pageContext.request?.headers.get(HTTP_HEADERS.XZubyProps);
|
|
81
82
|
if (propsHeader !== 'true')
|
|
82
83
|
return;
|
|
83
|
-
return new Response(JSON.stringify(pageContext.props, null, 2), {
|
|
84
|
+
return this.injectHeaders(new Response(JSON.stringify(pageContext.props, null, 2), {
|
|
84
85
|
status: pageContext.statusCode || 200,
|
|
85
86
|
headers: {
|
|
86
87
|
'Content-Type': 'application/json',
|
|
87
88
|
},
|
|
88
|
-
});
|
|
89
|
+
}), pageContext);
|
|
89
90
|
}
|
|
90
91
|
async executeEntry(pageContext) {
|
|
91
92
|
// Load page template component
|
|
@@ -118,6 +119,7 @@ export default class ZubyRenderer {
|
|
|
118
119
|
const layoutChildren = layoutComponent({
|
|
119
120
|
children: innerLayoutComponent({
|
|
120
121
|
innerHtml: entryHtml,
|
|
122
|
+
context: pageContext,
|
|
121
123
|
}),
|
|
122
124
|
context: pageContext,
|
|
123
125
|
});
|
|
@@ -147,12 +149,12 @@ export default class ZubyRenderer {
|
|
|
147
149
|
jsImports.forEach((imp) => {
|
|
148
150
|
html = html.replace(/(<\/head>)/, `<script type="module" src="${imp}" defer></script>$1`);
|
|
149
151
|
});
|
|
150
|
-
return new Response(html, {
|
|
152
|
+
return this.injectHeaders(new Response(html, {
|
|
151
153
|
status: pageContext.statusCode || 200,
|
|
152
154
|
headers: {
|
|
153
155
|
'Content-Type': 'text/html;charset=UTF-8',
|
|
154
156
|
},
|
|
155
|
-
});
|
|
157
|
+
}), pageContext);
|
|
156
158
|
}
|
|
157
159
|
createPageContext(req) {
|
|
158
160
|
// Paths starting with /_props are special case.
|
|
@@ -161,7 +163,7 @@ export default class ZubyRenderer {
|
|
|
161
163
|
const pageUrl = new URL(req.url);
|
|
162
164
|
if (pageUrl.pathname.startsWith('/_props/')) {
|
|
163
165
|
pageUrl.pathname = pageUrl.pathname.replace(/^\/_props\//, '/');
|
|
164
|
-
req.headers.set(
|
|
166
|
+
req.headers.set(HTTP_HEADERS.XZubyProps, 'true');
|
|
165
167
|
}
|
|
166
168
|
return new ZubyPageContext({
|
|
167
169
|
url: pageUrl,
|
|
@@ -169,6 +171,9 @@ export default class ZubyRenderer {
|
|
|
169
171
|
statusCode: 200,
|
|
170
172
|
props: {},
|
|
171
173
|
zubyContext: this.zubyContext,
|
|
174
|
+
headers: new Headers({
|
|
175
|
+
[HTTP_HEADERS.XPoweredBy]: 'Zuby.js',
|
|
176
|
+
}),
|
|
172
177
|
});
|
|
173
178
|
}
|
|
174
179
|
async render(req) {
|
|
@@ -177,4 +182,15 @@ export default class ZubyRenderer {
|
|
|
177
182
|
(await this.executeProps(pageContext)) ||
|
|
178
183
|
(await this.executeEntry(pageContext)));
|
|
179
184
|
}
|
|
185
|
+
injectHeaders(res, pageContext) {
|
|
186
|
+
pageContext.headers.forEach((value, key) => {
|
|
187
|
+
if (res.headers.has(key))
|
|
188
|
+
return;
|
|
189
|
+
res.headers.set(key, value);
|
|
190
|
+
});
|
|
191
|
+
if (pageContext.cache) {
|
|
192
|
+
res.headers.set(HTTP_HEADERS.XZubyCacheTTL, pageContext.cache.toString());
|
|
193
|
+
}
|
|
194
|
+
return res;
|
|
195
|
+
}
|
|
180
196
|
}
|
package/server/zubyServer.d.ts
CHANGED
|
@@ -11,14 +11,16 @@ export default class ZubyServer {
|
|
|
11
11
|
protected logger: ZubyLogger;
|
|
12
12
|
protected renderer: ZubyRenderer;
|
|
13
13
|
protected middlewares: ZubyMiddleware[];
|
|
14
|
+
protected cache: Map<string, Response>;
|
|
14
15
|
constructor(options: ZubyServerOptions);
|
|
15
16
|
handle(nodeReq: NodeRequest, nodeRes: NodeResponse): Promise<void>;
|
|
16
|
-
clientDirMiddleware(req: NodeRequest, res: NodeResponse, next: () =>
|
|
17
|
-
serverDirMiddleware(nodeReq: NodeRequest, nodeRes: NodeResponse): Promise<void>;
|
|
17
|
+
clientDirMiddleware(req: NodeRequest, res: NodeResponse, next: () => any | Promise<any>): Promise<any>;
|
|
18
|
+
serverDirMiddleware(nodeReq: NodeRequest, nodeRes: NodeResponse, _next: () => any | Promise<any>): Promise<void>;
|
|
19
|
+
cacheRead(key: string): Response | undefined;
|
|
20
|
+
cacheWrite(key: string, response: Response): Response;
|
|
18
21
|
listen(): Promise<void>;
|
|
19
22
|
close(): Promise<void>;
|
|
20
|
-
|
|
21
|
-
trailingSlashMiddleware(req: NodeRequest, res: NodeResponse, next: () => void): void;
|
|
23
|
+
trailingSlashMiddleware(req: NodeRequest, res: NodeResponse, next: () => any | Promise<any>): any;
|
|
22
24
|
toRequest(nodeReq: NodeRequest): Promise<Request>;
|
|
23
25
|
toNodeResponse(res: Response, nodeRes: ServerResponse): Promise<void>;
|
|
24
26
|
use(middleware: ZubyMiddleware): void;
|
package/server/zubyServer.js
CHANGED
|
@@ -7,14 +7,15 @@ import { createReadStream, existsSync, statSync } from 'fs';
|
|
|
7
7
|
import { mimeTypes } from './mimeTypes.js';
|
|
8
8
|
import { createLogger } from '../logger/index.js';
|
|
9
9
|
import ZubyRenderer from './zubyRenderer.js';
|
|
10
|
+
import { HTTP_HEADERS } from '../constants.js';
|
|
10
11
|
export default class ZubyServer {
|
|
11
12
|
constructor(options) {
|
|
13
|
+
this.cache = new Map();
|
|
12
14
|
this.options = options;
|
|
13
15
|
this.options.responseHeaders = this.options.responseHeaders || {};
|
|
14
16
|
this.logger = options.logger || createLogger('info');
|
|
15
17
|
this.renderer = options.renderer || new ZubyRenderer(options.outDir);
|
|
16
18
|
this.middlewares = [
|
|
17
|
-
this.defaultHeadersMiddleware.bind(this),
|
|
18
19
|
this.trailingSlashMiddleware.bind(this),
|
|
19
20
|
this.clientDirMiddleware.bind(this),
|
|
20
21
|
this.serverDirMiddleware.bind(this),
|
|
@@ -103,11 +104,38 @@ export default class ZubyServer {
|
|
|
103
104
|
res.end();
|
|
104
105
|
});
|
|
105
106
|
}
|
|
106
|
-
async serverDirMiddleware(nodeReq, nodeRes) {
|
|
107
|
+
async serverDirMiddleware(nodeReq, nodeRes, _next) {
|
|
107
108
|
const req = await this.toRequest(nodeReq);
|
|
108
|
-
const
|
|
109
|
+
const cacheKey = new URL(req.url, 'http://localhost').pathname;
|
|
110
|
+
const res = this.cacheRead(cacheKey) || this.cacheWrite(cacheKey, await this.renderer.render(req));
|
|
109
111
|
return this.toNodeResponse(res, nodeRes);
|
|
110
112
|
}
|
|
113
|
+
cacheRead(key) {
|
|
114
|
+
const cachedResponse = this.cache.get(key)?.clone();
|
|
115
|
+
if (!cachedResponse)
|
|
116
|
+
return undefined;
|
|
117
|
+
const cachedTime = Number(cachedResponse.headers.get(HTTP_HEADERS.XZubyCachedTime));
|
|
118
|
+
const cacheTTL = Number(cachedResponse.headers.get(HTTP_HEADERS.XZubyCacheTTL));
|
|
119
|
+
const age = Math.ceil((new Date().getTime() - cachedTime) / 1000);
|
|
120
|
+
if (age > cacheTTL) {
|
|
121
|
+
this.cache.delete(key);
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
cachedResponse.headers.set(HTTP_HEADERS.Age, age.toString());
|
|
125
|
+
cachedResponse.headers.set(HTTP_HEADERS.XZubyCache, 'HIT');
|
|
126
|
+
return cachedResponse;
|
|
127
|
+
}
|
|
128
|
+
cacheWrite(key, response) {
|
|
129
|
+
const cacheTTL = Number(response.headers.get(HTTP_HEADERS.XZubyCacheTTL));
|
|
130
|
+
if (!cacheTTL) {
|
|
131
|
+
response.headers.set(HTTP_HEADERS.XZubyCache, 'BYPASS');
|
|
132
|
+
return response;
|
|
133
|
+
}
|
|
134
|
+
response.headers.set(HTTP_HEADERS.XZubyCachedTime, new Date().getTime().toString());
|
|
135
|
+
response.headers.set(HTTP_HEADERS.XZubyCache, 'MISS');
|
|
136
|
+
this.cache.set(key, response.clone());
|
|
137
|
+
return response;
|
|
138
|
+
}
|
|
111
139
|
async listen() {
|
|
112
140
|
const serverPromise = new Promise((resolve, reject) => {
|
|
113
141
|
this.server.listen(this.options.port, this.options.host, 511, () => {
|
|
@@ -129,10 +157,6 @@ export default class ZubyServer {
|
|
|
129
157
|
});
|
|
130
158
|
});
|
|
131
159
|
}
|
|
132
|
-
defaultHeadersMiddleware(_req, res, next) {
|
|
133
|
-
res.setHeader('X-Powered-By', 'Zuby.js');
|
|
134
|
-
next();
|
|
135
|
-
}
|
|
136
160
|
trailingSlashMiddleware(req, res, next) {
|
|
137
161
|
// Redirect to path with trailing slash for consistency
|
|
138
162
|
const [path, query] = req.url?.split('?') || [];
|