structured-fw 1.2.2 → 1.3.0
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 +14 -0
- package/README.md +1 -1
- package/build/system/server/Application.js +1 -1
- package/build/system/server/Document.d.ts +1 -1
- package/build/system/server/Document.js +2 -2
- package/build/system/server/Request.d.ts +1 -0
- package/build/system/server/Request.js +39 -14
- package/build/system/types/request.types.d.ts +1 -1
- package/build/system/types/structured.types.d.ts +6 -0
- package/package.json +1 -1
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
|
}
|
package/README.md
CHANGED
|
@@ -290,7 +290,7 @@ type RequestContext<Body extends LooseObject | undefined = LooseObject> = {
|
|
|
290
290
|
getArgs: PostedDataDecoded,
|
|
291
291
|
|
|
292
292
|
// send given data as a response
|
|
293
|
-
respondWith: (data: any) => void
|
|
293
|
+
respondWith: (data: any) => Promise<void>,
|
|
294
294
|
|
|
295
295
|
// redirect to given url, with given statusCode
|
|
296
296
|
redirect: (to: string, statusCode?: number) => void,
|
|
@@ -158,7 +158,7 @@ export class Application {
|
|
|
158
158
|
prev[curr] = document.children[0].data[curr];
|
|
159
159
|
return prev;
|
|
160
160
|
}, {}) : {});
|
|
161
|
-
ctx.respondWith({
|
|
161
|
+
await ctx.respondWith({
|
|
162
162
|
html: document.children[0].dom[unwrap ? 'innerHTML' : 'outerHTML'],
|
|
163
163
|
initializers: document.initInitializers(),
|
|
164
164
|
data: exportedData
|
|
@@ -24,7 +24,7 @@ export declare class Document extends Component<{
|
|
|
24
24
|
body(): string;
|
|
25
25
|
initInitializers(): Record<string, string>;
|
|
26
26
|
private initClientConfig;
|
|
27
|
-
toString(): string
|
|
27
|
+
toString(): Promise<string>;
|
|
28
28
|
allocateId(): string;
|
|
29
29
|
loadView(pathRelative: string, data?: LooseObject): Promise<Document>;
|
|
30
30
|
loadComponent(componentName: string, data?: LooseObject): Promise<Document>;
|
|
@@ -59,8 +59,8 @@ export class Document extends Component {
|
|
|
59
59
|
const clientConfString = `<script type="application/javascript">window.structuredClientConfig = ${JSON.stringify(clientConf)}</script>`;
|
|
60
60
|
this.head.add(clientConfString);
|
|
61
61
|
}
|
|
62
|
-
toString() {
|
|
63
|
-
this.emit('beforeRender');
|
|
62
|
+
async toString() {
|
|
63
|
+
await this.emit('beforeRender');
|
|
64
64
|
if (!this.initializersInitialized) {
|
|
65
65
|
this.initInitializers();
|
|
66
66
|
this.initClientConfig();
|
|
@@ -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:
|
|
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(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) => {
|
|
@@ -134,7 +133,7 @@ export class Request {
|
|
|
134
133
|
response.statusCode = 404;
|
|
135
134
|
const res = await this.pageNotFoundCallback.apply(this.app, [context]);
|
|
136
135
|
if (res instanceof Document) {
|
|
137
|
-
context.respondWith(res);
|
|
136
|
+
await context.respondWith(res);
|
|
138
137
|
}
|
|
139
138
|
}
|
|
140
139
|
};
|
|
@@ -157,7 +156,7 @@ export class Request {
|
|
|
157
156
|
try {
|
|
158
157
|
const response = await handler.callback.apply(handler.scope, [context]);
|
|
159
158
|
if (!context.response.headersSent) {
|
|
160
|
-
context.respondWith(response);
|
|
159
|
+
await context.respondWith(response);
|
|
161
160
|
}
|
|
162
161
|
}
|
|
163
162
|
catch (e) {
|
|
@@ -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,7 +24,7 @@ export type RequestContext<Body extends LooseObject | undefined = LooseObject> =
|
|
|
24
24
|
isAjax: boolean;
|
|
25
25
|
getArgs: PostedDataDecoded;
|
|
26
26
|
timeStart: number;
|
|
27
|
-
respondWith: (data: any) => void
|
|
27
|
+
respondWith: (data: any) => Promise<void>;
|
|
28
28
|
redirect: (to: string, statusCode?: number) => void;
|
|
29
29
|
show404: () => Promise<void>;
|
|
30
30
|
};
|
|
@@ -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.0",
|
|
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",
|