structured-fw 1.2.3 → 1.3.1
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.ts
CHANGED
|
@@ -42,5 +42,19 @@ export const config: StructuredConfig = {
|
|
|
42
42
|
// used by Document.push, can be preload or preconnect
|
|
43
43
|
linkHeaderRel : 'preload'
|
|
44
44
|
},
|
|
45
|
+
gzip: {
|
|
46
|
+
enabled: true, // whether to enable response gzip compression
|
|
47
|
+
// compress only listed types
|
|
48
|
+
types: [
|
|
49
|
+
'text/html',
|
|
50
|
+
'text/xml',
|
|
51
|
+
'text/plain',
|
|
52
|
+
'text/css',
|
|
53
|
+
'application/javascript',
|
|
54
|
+
'application/json'
|
|
55
|
+
],
|
|
56
|
+
minSize: 10240, // compress only if response is at least minSize bytes
|
|
57
|
+
compressionLevel: 4, // higher value = greater compression, slower
|
|
58
|
+
},
|
|
45
59
|
runtime: 'Node.js'
|
|
46
60
|
}
|
|
@@ -79,9 +79,16 @@ export class ClientComponent extends EventEmitter {
|
|
|
79
79
|
}
|
|
80
80
|
this.isReady = true;
|
|
81
81
|
if (this.isRoot || isRedrawRoot) {
|
|
82
|
-
|
|
82
|
+
this.on('initializerExecuted', () => {
|
|
83
|
+
this.emit('ready');
|
|
84
|
+
});
|
|
85
|
+
requestAnimationFrame(() => {
|
|
86
|
+
this.runInitializer(isRedraw);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
this.emit('ready');
|
|
83
91
|
}
|
|
84
|
-
this.emit('ready');
|
|
85
92
|
}
|
|
86
93
|
reset() {
|
|
87
94
|
this.data = {};
|
|
@@ -20,4 +20,5 @@ export declare class Request {
|
|
|
20
20
|
static queryStringDecode(queryString: string, initialValue?: PostedDataDecoded, trimValues?: boolean): PostedDataDecoded;
|
|
21
21
|
static parseBodyMultipart(bodyRaw: string, boundary: string): PostedDataDecoded;
|
|
22
22
|
static multipartBodyFiles(bodyRaw: string, boundary: string): Record<string, RequestBodyFile>;
|
|
23
|
+
private sendResponse;
|
|
23
24
|
}
|
|
@@ -3,6 +3,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Buffer } from "node:buffer";
|
|
5
5
|
import { Document } from "./Document.js";
|
|
6
|
+
import zlib from "node:zlib";
|
|
6
7
|
export class Request {
|
|
7
8
|
constructor(app) {
|
|
8
9
|
this.pageNotFoundCallback = async ({ response }) => {
|
|
@@ -107,23 +108,21 @@ export class Request {
|
|
|
107
108
|
cookies: this.app.cookies.parse(request),
|
|
108
109
|
timeStart: new Date().getTime(),
|
|
109
110
|
isAjax: request.headers['x-requested-with'] == 'xmlhttprequest',
|
|
110
|
-
respondWith: async
|
|
111
|
+
respondWith: async (data) => {
|
|
111
112
|
if (typeof data === 'string' || Buffer.isBuffer(data)) {
|
|
112
|
-
|
|
113
|
+
this.sendResponse(request, response, data, 'text/plain; charset=utf-8');
|
|
113
114
|
}
|
|
114
115
|
else if (typeof data === 'number') {
|
|
115
|
-
|
|
116
|
+
this.sendResponse(request, response, data.toString(), 'text/plain; charset=utf-8');
|
|
116
117
|
}
|
|
117
118
|
else if (data instanceof Document) {
|
|
118
|
-
response.
|
|
119
|
-
response.write(await data.toString());
|
|
119
|
+
this.sendResponse(request, response, await data.toString(), 'text/html; charset=utf-8');
|
|
120
120
|
}
|
|
121
121
|
else if (data === undefined || data === null) {
|
|
122
|
-
|
|
122
|
+
this.sendResponse(request, response, '', 'text/plain; charset=utf-8');
|
|
123
123
|
}
|
|
124
124
|
else {
|
|
125
|
-
response.
|
|
126
|
-
response.write(JSON.stringify(data, null, 4));
|
|
125
|
+
this.sendResponse(request, response, JSON.stringify(data, null, 4), 'application/json; charset=utf-8');
|
|
127
126
|
}
|
|
128
127
|
},
|
|
129
128
|
redirect: (to, statusCode = 302) => {
|
|
@@ -175,13 +174,14 @@ export class Request {
|
|
|
175
174
|
if (existsSync(assetPath)) {
|
|
176
175
|
await this.app.emit('beforeAssetAccess', context);
|
|
177
176
|
const extension = (context.request.url || '').split('.').pop();
|
|
177
|
+
let contentType = 'application/javascript';
|
|
178
178
|
if (extension) {
|
|
179
|
-
const
|
|
180
|
-
if (
|
|
181
|
-
|
|
179
|
+
const typeByExtension = this.app.contentType(extension);
|
|
180
|
+
if (typeByExtension) {
|
|
181
|
+
contentType = typeByExtension;
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
-
|
|
184
|
+
this.sendResponse(request, response, readFileSync(assetPath), contentType);
|
|
185
185
|
staticAsset = true;
|
|
186
186
|
await this.app.emit('afterAssetAccess', context);
|
|
187
187
|
}
|
|
@@ -395,4 +395,29 @@ export class Request {
|
|
|
395
395
|
});
|
|
396
396
|
return files;
|
|
397
397
|
}
|
|
398
|
+
sendResponse(request, response, buffer, contentType) {
|
|
399
|
+
const mimeType = contentType.split(';')[0];
|
|
400
|
+
const gzipResponse = this.app.config.gzip.enabled &&
|
|
401
|
+
this.app.config.gzip.types.includes(mimeType) &&
|
|
402
|
+
request.headers['accept-encoding']?.includes('gzip') &&
|
|
403
|
+
buffer.length >= this.app.config.gzip.minSize;
|
|
404
|
+
if (!response.hasHeader('Content-Type')) {
|
|
405
|
+
response.setHeader('Content-Type', contentType);
|
|
406
|
+
}
|
|
407
|
+
if (typeof buffer === 'string') {
|
|
408
|
+
buffer = Buffer.from(buffer, 'utf-8');
|
|
409
|
+
}
|
|
410
|
+
if (gzipResponse) {
|
|
411
|
+
response.setHeader('Content-Encoding', 'gzip');
|
|
412
|
+
const compressed = zlib.gzipSync(buffer, {
|
|
413
|
+
level: this.app.config.gzip.compressionLevel
|
|
414
|
+
});
|
|
415
|
+
response.setHeader('Content-Length', compressed.length);
|
|
416
|
+
response.write(compressed);
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
response.setHeader('Content-Length', buffer.length);
|
|
420
|
+
response.write(buffer);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
398
423
|
}
|
|
@@ -24,6 +24,12 @@ export type StructuredConfig = {
|
|
|
24
24
|
port: number;
|
|
25
25
|
linkHeaderRel: 'preload' | 'preconnect';
|
|
26
26
|
};
|
|
27
|
+
gzip: {
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
types: Array<string>;
|
|
30
|
+
minSize: number;
|
|
31
|
+
compressionLevel: number;
|
|
32
|
+
};
|
|
27
33
|
readonly runtime: 'Node.js' | 'Deno';
|
|
28
34
|
};
|
|
29
35
|
export type StructuredClientConfig = {
|
package/package.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"type": "module",
|
|
21
21
|
"main": "build/index",
|
|
22
|
-
"version": "1.
|
|
22
|
+
"version": "1.3.1",
|
|
23
23
|
"scripts": {
|
|
24
24
|
"develop": "tsc --watch",
|
|
25
25
|
"startDev": "cd build && nodemon --watch '../app/**/*' --watch '../build/**/*' -e js,html,hbs,css index.js",
|