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
  }
@@ -1,2 +1,4 @@
1
1
  import { ClientApplication } from './ClientApplication.js';
2
- new ClientApplication();
2
+ window.addEventListener('load', () => {
3
+ new ClientApplication();
4
+ });
@@ -79,9 +79,16 @@ export class ClientComponent extends EventEmitter {
79
79
  }
80
80
  this.isReady = true;
81
81
  if (this.isRoot || isRedrawRoot) {
82
- await this.runInitializer(isRedraw);
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 function (data) {
111
+ respondWith: async (data) => {
111
112
  if (typeof data === 'string' || Buffer.isBuffer(data)) {
112
- response.write(data);
113
+ this.sendResponse(request, response, data, 'text/plain; charset=utf-8');
113
114
  }
114
115
  else if (typeof data === 'number') {
115
- response.write(data.toString());
116
+ this.sendResponse(request, response, data.toString(), 'text/plain; charset=utf-8');
116
117
  }
117
118
  else if (data instanceof Document) {
118
- response.setHeader('Content-Type', 'text/html');
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
- response.write('');
122
+ this.sendResponse(request, response, '', 'text/plain; charset=utf-8');
123
123
  }
124
124
  else {
125
- response.setHeader('Content-Type', 'application/json');
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 contentType = this.app.contentType(extension);
180
- if (contentType) {
181
- response.setHeader('Content-Type', contentType);
179
+ const typeByExtension = this.app.contentType(extension);
180
+ if (typeByExtension) {
181
+ contentType = typeByExtension;
182
182
  }
183
183
  }
184
- response.write(readFileSync(assetPath));
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.2.3",
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",