zuby 1.0.40 → 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.js +9 -3
- package/constants.d.ts +24 -4
- package/constants.js +27 -4
- package/package.json +1 -1
- package/pageContext/index.d.ts +6 -0
- package/pageContext/index.js +16 -2
- package/plugins/manifestPlugin/index.js +4 -4
- package/plugins/prerenderPlugin/index.js +2 -2
- package/server/index.js +125 -38
- package/server/zubyDevRenderer.js +4 -0
- package/server/zubyDevServer.js +0 -1
- package/server/zubyRenderer.d.ts +1 -0
- package/server/zubyRenderer.js +28 -13
- package/server/zubyServer.d.ts +6 -4
- package/server/zubyServer.js +31 -7
package/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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';
|
|
@@ -112,7 +112,7 @@ export const mergeDefaultConfig = async (config) => {
|
|
|
112
112
|
config.vite.customLogger = config.vite.customLogger ?? config.customLogger;
|
|
113
113
|
config.vite.server = config.vite.server ?? config.server;
|
|
114
114
|
config.vite.publicDir = config.vite.publicDir ?? config.publicDir;
|
|
115
|
-
config.vite.build.ssrManifest =
|
|
115
|
+
config.vite.build.ssrManifest = BUILD_CHUNKS_MANIFEST;
|
|
116
116
|
config.vite.build.ssrEmitAssets = config.vite.build.ssrEmitAssets ?? true;
|
|
117
117
|
config.vite.build.modulePreload = { polyfill: false };
|
|
118
118
|
config.vite.optimizeDeps = config.vite.optimizeDeps ?? {};
|
|
@@ -133,5 +133,11 @@ export const mergeDefaultConfig = async (config) => {
|
|
|
133
133
|
* before removing any of them.
|
|
134
134
|
*/
|
|
135
135
|
export const getBuiltInPlugins = () => {
|
|
136
|
-
return [
|
|
136
|
+
return [
|
|
137
|
+
contextPlugin(),
|
|
138
|
+
compileTimePlugin(),
|
|
139
|
+
chunkNamingPlugin(),
|
|
140
|
+
manifestPlugin(),
|
|
141
|
+
prerenderPlugin(),
|
|
142
|
+
];
|
|
137
143
|
};
|
package/constants.d.ts
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
export declare const ZUBY_CONFIG_FILE = "zuby.config.mjs";
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
4
|
-
export declare const
|
|
5
|
-
export declare const
|
|
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,5 +1,28 @@
|
|
|
1
1
|
export const ZUBY_CONFIG_FILE = 'zuby.config.mjs';
|
|
2
|
-
export const
|
|
3
|
-
export const
|
|
4
|
-
export const
|
|
5
|
-
export const
|
|
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
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
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { renameSync } from 'fs';
|
|
2
2
|
import { normalizePath } from '../../utils/pathUtils.js';
|
|
3
3
|
import { join } from 'path';
|
|
4
|
-
import {
|
|
4
|
+
import { BUILD_CHUNKS_MANIFEST, CLIENT_CHUNKS_MANIFEST, SERVER_CHUNKS_MANIFEST, } from '../../constants.js';
|
|
5
5
|
/**
|
|
6
6
|
* This is internal plugin
|
|
7
7
|
* which generated and moves required manifest files.
|
|
@@ -21,8 +21,8 @@ export default function manifestPlugin() {
|
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
23
|
// Move chunks manifest files
|
|
24
|
-
renameSync(normalizePath(join(viteConfig.build.outDir, '..', 'client',
|
|
25
|
-
renameSync(normalizePath(join(viteConfig.build.outDir, '..', 'server',
|
|
26
|
-
}
|
|
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
27
|
};
|
|
28
28
|
}
|
|
@@ -10,7 +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 {
|
|
13
|
+
import { SERVER_CHUNKS_MANIFEST } from '../../constants.js';
|
|
14
14
|
/**
|
|
15
15
|
* This is internal plugin
|
|
16
16
|
* that pre-renders the pages during the build.
|
|
@@ -31,7 +31,7 @@ export default function prerenderPlugin() {
|
|
|
31
31
|
}
|
|
32
32
|
const startTime = performance.now();
|
|
33
33
|
const { srcDir, outDir, customLogger, prerenderPaths, site, output } = await getZubyInternalConfig();
|
|
34
|
-
const chunksManifestPath = resolve(outDir,
|
|
34
|
+
const chunksManifestPath = resolve(outDir, SERVER_CHUNKS_MANIFEST);
|
|
35
35
|
const chunksManifest = existsSync(chunksManifestPath)
|
|
36
36
|
? JSON.parse(readFileSync(chunksManifestPath, 'utf-8'))
|
|
37
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
|
|
@@ -8390,7 +8404,30 @@ function findMatchingTemplate(templates, path2) {
|
|
|
8390
8404
|
}
|
|
8391
8405
|
|
|
8392
8406
|
// src/constants.ts
|
|
8393
|
-
var
|
|
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
|
+
};
|
|
8394
8431
|
|
|
8395
8432
|
// src/server/zubyRenderer.ts
|
|
8396
8433
|
var ZubyRenderer = class {
|
|
@@ -8412,7 +8449,7 @@ var ZubyRenderer = class {
|
|
|
8412
8449
|
if (this.entryClientCss) {
|
|
8413
8450
|
this.entryClientCss = `/chunks/${basename(this.entryClientCss || "")}`;
|
|
8414
8451
|
}
|
|
8415
|
-
const manifestPath = resolve2(this.outDir,
|
|
8452
|
+
const manifestPath = resolve2(this.outDir, CLIENT_CHUNKS_MANIFEST);
|
|
8416
8453
|
if (existsSync(manifestPath)) {
|
|
8417
8454
|
this.chunksManifest = JSON.parse(readFileSync2(manifestPath, "utf-8"));
|
|
8418
8455
|
}
|
|
@@ -8440,33 +8477,42 @@ var ZubyRenderer = class {
|
|
|
8440
8477
|
if (!handlerResult)
|
|
8441
8478
|
return;
|
|
8442
8479
|
if (handlerResult instanceof Response) {
|
|
8443
|
-
return handlerResult;
|
|
8480
|
+
return this.injectHeaders(handlerResult, pageContext);
|
|
8444
8481
|
}
|
|
8445
8482
|
if (typeof handlerResult === "string") {
|
|
8446
|
-
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), {
|
|
8447
8495
|
status: pageContext.statusCode || 200,
|
|
8448
8496
|
headers: {
|
|
8449
|
-
"Content-Type": "
|
|
8497
|
+
"Content-Type": "application/json;charset=UTF-8"
|
|
8450
8498
|
}
|
|
8451
|
-
})
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
status: pageContext.statusCode || 200,
|
|
8455
|
-
headers: {
|
|
8456
|
-
"Content-Type": "application/json;charset=UTF-8"
|
|
8457
|
-
}
|
|
8458
|
-
});
|
|
8499
|
+
}),
|
|
8500
|
+
pageContext
|
|
8501
|
+
);
|
|
8459
8502
|
}
|
|
8460
8503
|
async executeProps(pageContext) {
|
|
8461
|
-
const propsHeader = pageContext.request?.headers.get(
|
|
8504
|
+
const propsHeader = pageContext.request?.headers.get(HTTP_HEADERS.XZubyProps);
|
|
8462
8505
|
if (propsHeader !== "true")
|
|
8463
8506
|
return;
|
|
8464
|
-
return
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
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
|
+
);
|
|
8470
8516
|
}
|
|
8471
8517
|
async executeEntry(pageContext) {
|
|
8472
8518
|
const pages = this.zubyContext?.templates?.pages || [];
|
|
@@ -8494,7 +8540,8 @@ var ZubyRenderer = class {
|
|
|
8494
8540
|
) || "";
|
|
8495
8541
|
const layoutChildren = layoutComponent({
|
|
8496
8542
|
children: innerLayoutComponent({
|
|
8497
|
-
innerHtml: entryHtml
|
|
8543
|
+
innerHtml: entryHtml,
|
|
8544
|
+
context: pageContext
|
|
8498
8545
|
}),
|
|
8499
8546
|
context: pageContext
|
|
8500
8547
|
});
|
|
@@ -8518,42 +8565,59 @@ var ZubyRenderer = class {
|
|
|
8518
8565
|
jsImports.forEach((imp) => {
|
|
8519
8566
|
html = html.replace(/(<\/head>)/, `<script type="module" src="${imp}" defer></script>$1`);
|
|
8520
8567
|
});
|
|
8521
|
-
return
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
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
|
+
);
|
|
8527
8577
|
}
|
|
8528
8578
|
createPageContext(req) {
|
|
8529
8579
|
const pageUrl = new URL(req.url);
|
|
8530
8580
|
if (pageUrl.pathname.startsWith("/_props/")) {
|
|
8531
8581
|
pageUrl.pathname = pageUrl.pathname.replace(/^\/_props\//, "/");
|
|
8532
|
-
req.headers.set(
|
|
8582
|
+
req.headers.set(HTTP_HEADERS.XZubyProps, "true");
|
|
8533
8583
|
}
|
|
8534
8584
|
return new ZubyPageContext({
|
|
8535
8585
|
url: pageUrl,
|
|
8536
8586
|
request: req,
|
|
8537
8587
|
statusCode: 200,
|
|
8538
8588
|
props: {},
|
|
8539
|
-
zubyContext: this.zubyContext
|
|
8589
|
+
zubyContext: this.zubyContext,
|
|
8590
|
+
headers: new Headers({
|
|
8591
|
+
[HTTP_HEADERS.XPoweredBy]: "Zuby.js"
|
|
8592
|
+
})
|
|
8540
8593
|
});
|
|
8541
8594
|
}
|
|
8542
8595
|
async render(req) {
|
|
8543
8596
|
const pageContext = this.createPageContext(req);
|
|
8544
8597
|
return await this.executeHandlers(pageContext) || await this.executeProps(pageContext) || await this.executeEntry(pageContext);
|
|
8545
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
|
+
}
|
|
8546
8610
|
};
|
|
8547
8611
|
|
|
8548
8612
|
// src/server/zubyServer.ts
|
|
8549
8613
|
var ZubyServer = class {
|
|
8550
8614
|
constructor(options) {
|
|
8615
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
8551
8616
|
this.options = options;
|
|
8552
8617
|
this.options.responseHeaders = this.options.responseHeaders || {};
|
|
8553
8618
|
this.logger = options.logger || createLogger("info");
|
|
8554
8619
|
this.renderer = options.renderer || new ZubyRenderer(options.outDir);
|
|
8555
8620
|
this.middlewares = [
|
|
8556
|
-
this.defaultHeadersMiddleware.bind(this),
|
|
8557
8621
|
this.trailingSlashMiddleware.bind(this),
|
|
8558
8622
|
this.clientDirMiddleware.bind(this),
|
|
8559
8623
|
this.serverDirMiddleware.bind(this)
|
|
@@ -8633,11 +8697,38 @@ var ZubyServer = class {
|
|
|
8633
8697
|
res.end();
|
|
8634
8698
|
});
|
|
8635
8699
|
}
|
|
8636
|
-
async serverDirMiddleware(nodeReq, nodeRes) {
|
|
8700
|
+
async serverDirMiddleware(nodeReq, nodeRes, _next) {
|
|
8637
8701
|
const req = await this.toRequest(nodeReq);
|
|
8638
|
-
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));
|
|
8639
8704
|
return this.toNodeResponse(res, nodeRes);
|
|
8640
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
|
+
}
|
|
8641
8732
|
async listen() {
|
|
8642
8733
|
const serverPromise = new Promise((resolve5, reject) => {
|
|
8643
8734
|
this.server.listen(this.options.port, this.options.host, 511, () => {
|
|
@@ -8659,10 +8750,6 @@ var ZubyServer = class {
|
|
|
8659
8750
|
});
|
|
8660
8751
|
});
|
|
8661
8752
|
}
|
|
8662
|
-
defaultHeadersMiddleware(_req, res, next) {
|
|
8663
|
-
res.setHeader("X-Powered-By", "Zuby.js");
|
|
8664
|
-
next();
|
|
8665
|
-
}
|
|
8666
8753
|
trailingSlashMiddleware(req, res, next) {
|
|
8667
8754
|
const [path2, query] = req.url?.split("?") || [];
|
|
8668
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,7 +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 {
|
|
8
|
+
import { CLIENT_CHUNKS_MANIFEST, HTTP_HEADERS } from '../constants.js';
|
|
9
9
|
export default class ZubyRenderer {
|
|
10
10
|
constructor(outDir) {
|
|
11
11
|
this.outDir = outDir;
|
|
@@ -25,7 +25,7 @@ export default class ZubyRenderer {
|
|
|
25
25
|
if (this.entryClientCss) {
|
|
26
26
|
this.entryClientCss = `/chunks/${basename(this.entryClientCss || '')}`;
|
|
27
27
|
}
|
|
28
|
-
const manifestPath = resolve(this.outDir,
|
|
28
|
+
const manifestPath = resolve(this.outDir, CLIENT_CHUNKS_MANIFEST);
|
|
29
29
|
if (existsSync(manifestPath)) {
|
|
30
30
|
this.chunksManifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
31
31
|
}
|
|
@@ -57,36 +57,36 @@ export default class ZubyRenderer {
|
|
|
57
57
|
return;
|
|
58
58
|
// If handler returned a response, we can immediately return it
|
|
59
59
|
if (handlerResult instanceof Response) {
|
|
60
|
-
return handlerResult;
|
|
60
|
+
return this.injectHeaders(handlerResult, pageContext);
|
|
61
61
|
}
|
|
62
62
|
// If handler returned string,
|
|
63
63
|
// we'll assume that user wants to return html response
|
|
64
64
|
if (typeof handlerResult === 'string') {
|
|
65
|
-
return new Response(handlerResult.toString(), {
|
|
65
|
+
return this.injectHeaders(new Response(handlerResult.toString(), {
|
|
66
66
|
status: pageContext.statusCode || 200,
|
|
67
67
|
headers: {
|
|
68
68
|
'Content-Type': 'text/html;charset=UTF-8',
|
|
69
69
|
},
|
|
70
|
-
});
|
|
70
|
+
}), pageContext);
|
|
71
71
|
}
|
|
72
72
|
// We'll try to serialize everything else as JSON
|
|
73
|
-
return new Response(JSON.stringify(handlerResult, null, 2), {
|
|
73
|
+
return this.injectHeaders(new Response(JSON.stringify(handlerResult, null, 2), {
|
|
74
74
|
status: pageContext.statusCode || 200,
|
|
75
75
|
headers: {
|
|
76
76
|
'Content-Type': 'application/json;charset=UTF-8',
|
|
77
77
|
},
|
|
78
|
-
});
|
|
78
|
+
}), pageContext);
|
|
79
79
|
}
|
|
80
80
|
async executeProps(pageContext) {
|
|
81
|
-
const propsHeader = pageContext.request?.headers.get(
|
|
81
|
+
const propsHeader = pageContext.request?.headers.get(HTTP_HEADERS.XZubyProps);
|
|
82
82
|
if (propsHeader !== 'true')
|
|
83
83
|
return;
|
|
84
|
-
return new Response(JSON.stringify(pageContext.props, null, 2), {
|
|
84
|
+
return this.injectHeaders(new Response(JSON.stringify(pageContext.props, null, 2), {
|
|
85
85
|
status: pageContext.statusCode || 200,
|
|
86
86
|
headers: {
|
|
87
87
|
'Content-Type': 'application/json',
|
|
88
88
|
},
|
|
89
|
-
});
|
|
89
|
+
}), pageContext);
|
|
90
90
|
}
|
|
91
91
|
async executeEntry(pageContext) {
|
|
92
92
|
// Load page template component
|
|
@@ -119,6 +119,7 @@ export default class ZubyRenderer {
|
|
|
119
119
|
const layoutChildren = layoutComponent({
|
|
120
120
|
children: innerLayoutComponent({
|
|
121
121
|
innerHtml: entryHtml,
|
|
122
|
+
context: pageContext,
|
|
122
123
|
}),
|
|
123
124
|
context: pageContext,
|
|
124
125
|
});
|
|
@@ -148,12 +149,12 @@ export default class ZubyRenderer {
|
|
|
148
149
|
jsImports.forEach((imp) => {
|
|
149
150
|
html = html.replace(/(<\/head>)/, `<script type="module" src="${imp}" defer></script>$1`);
|
|
150
151
|
});
|
|
151
|
-
return new Response(html, {
|
|
152
|
+
return this.injectHeaders(new Response(html, {
|
|
152
153
|
status: pageContext.statusCode || 200,
|
|
153
154
|
headers: {
|
|
154
155
|
'Content-Type': 'text/html;charset=UTF-8',
|
|
155
156
|
},
|
|
156
|
-
});
|
|
157
|
+
}), pageContext);
|
|
157
158
|
}
|
|
158
159
|
createPageContext(req) {
|
|
159
160
|
// Paths starting with /_props are special case.
|
|
@@ -162,7 +163,7 @@ export default class ZubyRenderer {
|
|
|
162
163
|
const pageUrl = new URL(req.url);
|
|
163
164
|
if (pageUrl.pathname.startsWith('/_props/')) {
|
|
164
165
|
pageUrl.pathname = pageUrl.pathname.replace(/^\/_props\//, '/');
|
|
165
|
-
req.headers.set(
|
|
166
|
+
req.headers.set(HTTP_HEADERS.XZubyProps, 'true');
|
|
166
167
|
}
|
|
167
168
|
return new ZubyPageContext({
|
|
168
169
|
url: pageUrl,
|
|
@@ -170,6 +171,9 @@ export default class ZubyRenderer {
|
|
|
170
171
|
statusCode: 200,
|
|
171
172
|
props: {},
|
|
172
173
|
zubyContext: this.zubyContext,
|
|
174
|
+
headers: new Headers({
|
|
175
|
+
[HTTP_HEADERS.XPoweredBy]: 'Zuby.js',
|
|
176
|
+
}),
|
|
173
177
|
});
|
|
174
178
|
}
|
|
175
179
|
async render(req) {
|
|
@@ -178,4 +182,15 @@ export default class ZubyRenderer {
|
|
|
178
182
|
(await this.executeProps(pageContext)) ||
|
|
179
183
|
(await this.executeEntry(pageContext)));
|
|
180
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
|
+
}
|
|
181
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('?') || [];
|