vanilla-jet 1.3.2 → 1.4.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/.github/workflows/deploy.yml +18 -11
- package/CHANGELOG.md +106 -0
- package/README.md +141 -5
- package/ROADMAP_INTEGRAL.md +256 -0
- package/docs/benchmark-static.md +45 -0
- package/docs/deployment/Dockerfile.app.example +19 -0
- package/docs/deployment/docker-compose.nginx-node.example.yml +23 -0
- package/docs/deployment/nginx.default.conf.example +36 -0
- package/framework/dipper.js +5 -5
- package/framework/response.js +92 -15
- package/framework/router.js +257 -20
- package/framework/server.js +2 -1
- package/package.json +3 -2
- package/scripts/benchmark-static.js +201 -0
package/framework/dipper.js
CHANGED
|
@@ -214,8 +214,8 @@ Dipper.prototype.dequeueStyle = function(name, dependencies) {
|
|
|
214
214
|
if (obj.styles[name] != undefined) {
|
|
215
215
|
if (obj.enqueued_styles[name] != undefined) {
|
|
216
216
|
var item = obj.styles[name];
|
|
217
|
-
if (dependencies
|
|
218
|
-
_.each(item.
|
|
217
|
+
if (dependencies === true && Array.isArray(item.requires)) {
|
|
218
|
+
_.each(item.requires, function(dep) {
|
|
219
219
|
obj.dequeueStyle(dep);
|
|
220
220
|
});
|
|
221
221
|
}
|
|
@@ -233,8 +233,8 @@ Dipper.prototype.dequeueScript = function(name, dependencies) {
|
|
|
233
233
|
if (obj.scripts[name] != undefined) {
|
|
234
234
|
if (obj.enqueued_scripts[name] != undefined) {
|
|
235
235
|
var item = obj.scripts[name];
|
|
236
|
-
if (dependencies
|
|
237
|
-
_.each(item.
|
|
236
|
+
if (dependencies === true && Array.isArray(item.requires)) {
|
|
237
|
+
_.each(item.requires, function(dep) {
|
|
238
238
|
obj.dequeueScript(dep);
|
|
239
239
|
});
|
|
240
240
|
}
|
|
@@ -341,7 +341,7 @@ Dipper.prototype.includeAnimations = function() {
|
|
|
341
341
|
keys = Object.keys(obj.anims);
|
|
342
342
|
|
|
343
343
|
_.each(keys, function(anim) {
|
|
344
|
-
animsString += obj.
|
|
344
|
+
animsString += obj.includeAnimation(anim);
|
|
345
345
|
});
|
|
346
346
|
let baseAnimsString = `<script>'${animsString}'</script>`;
|
|
347
347
|
return baseAnimsString;
|
package/framework/response.js
CHANGED
|
@@ -3,18 +3,22 @@ let _ = require('underscore');
|
|
|
3
3
|
|
|
4
4
|
class Response {
|
|
5
5
|
|
|
6
|
-
constructor(res) {
|
|
6
|
+
constructor(res, options) {
|
|
7
7
|
|
|
8
8
|
this.res = null;
|
|
9
9
|
this.body = '';
|
|
10
10
|
this.status = 200;
|
|
11
11
|
this.headers = [];
|
|
12
12
|
this.autoRespond = true;
|
|
13
|
-
|
|
13
|
+
this.options = {};
|
|
14
|
+
this.init(res, options);
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
init(res) {
|
|
17
|
+
init(res, options) {
|
|
17
18
|
this.res = res;
|
|
19
|
+
this.options = Object.assign({
|
|
20
|
+
enable_precompressed_negotiation: false
|
|
21
|
+
}, options || {});
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
setBody(body) {
|
|
@@ -75,20 +79,93 @@ class Response {
|
|
|
75
79
|
let obj = this,
|
|
76
80
|
path = require("path"),
|
|
77
81
|
fs = require("fs");
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
let templatePath = 'pages/' + template;
|
|
83
|
+
let acceptEncoding = request.acceptEncoding || [];
|
|
84
|
+
let allowBrotli = Boolean(obj.options.enable_precompressed_negotiation);
|
|
85
|
+
let baseFilename = path.join(process.cwd(), 'public', templatePath);
|
|
86
|
+
let candidates = [];
|
|
87
|
+
|
|
88
|
+
if (allowBrotli && obj.supportsEncoding(acceptEncoding, 'br')) {
|
|
89
|
+
candidates.push({ filename: baseFilename + '.br', encoding: 'br' });
|
|
86
90
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
if (obj.supportsEncoding(acceptEncoding, 'gzip')) {
|
|
92
|
+
candidates.push({ filename: baseFilename + '.gz', encoding: 'gzip' });
|
|
93
|
+
}
|
|
94
|
+
candidates.push({ filename: baseFilename, encoding: '' });
|
|
95
|
+
|
|
96
|
+
let hasNegotiation = candidates.some((candidate) => candidate.encoding !== '');
|
|
97
|
+
obj.resolveFirstAvailableFile(candidates, (err, selectedFile) => {
|
|
98
|
+
if (err || !selectedFile) {
|
|
99
|
+
return obj.error404();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
obj.res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
103
|
+
if (hasNegotiation) {
|
|
104
|
+
obj.res.setHeader('Vary', 'Accept-Encoding');
|
|
105
|
+
}
|
|
106
|
+
if (selectedFile.encoding) {
|
|
107
|
+
obj.res.setHeader('Content-Encoding', selectedFile.encoding);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let fileStream = fs.createReadStream(selectedFile.filename);
|
|
111
|
+
fileStream.on('error', () => {
|
|
112
|
+
obj.error404();
|
|
113
|
+
});
|
|
114
|
+
fileStream.pipe(obj.res);
|
|
115
|
+
});
|
|
91
116
|
}
|
|
117
|
+
|
|
118
|
+
resolveFirstAvailableFile(candidates, callback) {
|
|
119
|
+
let fs = require("fs");
|
|
120
|
+
let index = 0;
|
|
121
|
+
function resolveCandidate() {
|
|
122
|
+
let currentCandidate = candidates[index];
|
|
123
|
+
if (!currentCandidate) {
|
|
124
|
+
return callback(new Error('File not found'));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fs.stat(currentCandidate.filename, (err, stats) => {
|
|
128
|
+
if (!err && stats && stats.isFile()) {
|
|
129
|
+
return callback(null, currentCandidate);
|
|
130
|
+
}
|
|
131
|
+
index = index + 1;
|
|
132
|
+
resolveCandidate();
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
resolveCandidate();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
supportsEncoding(acceptEncoding, encoding) {
|
|
140
|
+
if (!Array.isArray(acceptEncoding)) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let normalizedEncoding = String(encoding).toLowerCase();
|
|
145
|
+
return acceptEncoding.some((entry) => {
|
|
146
|
+
if (entry == null) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
let token = String(entry).toLowerCase();
|
|
150
|
+
let parts = token.split(';');
|
|
151
|
+
if (parts[0] !== normalizedEncoding) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let qValue = 1;
|
|
156
|
+
for (let idx = 1; idx < parts.length; idx = idx + 1) {
|
|
157
|
+
let part = parts[idx];
|
|
158
|
+
if (part.startsWith('q=')) {
|
|
159
|
+
let parsedQValue = parseFloat(part.slice(2));
|
|
160
|
+
if (!Number.isNaN(parsedQValue)) {
|
|
161
|
+
qValue = parsedQValue;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return qValue > 0;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
92
169
|
}
|
|
93
170
|
|
|
94
171
|
module.exports = Response;
|
package/framework/router.js
CHANGED
|
@@ -16,6 +16,12 @@ class Router {
|
|
|
16
16
|
this.defaultRoute = '';
|
|
17
17
|
this.server = server;
|
|
18
18
|
this.cwd = process.cwd();
|
|
19
|
+
this.staticBasePath = this.cwd.replace('core/framework', '');
|
|
20
|
+
this.staticMetadataCache = new Map();
|
|
21
|
+
this.staticResolutionCache = new Map();
|
|
22
|
+
this.staticFileWatchers = new Map();
|
|
23
|
+
this.staticMetadataMaxAgeMs = 1000;
|
|
24
|
+
this.staticStreamChunkSize = 128 * 1024;
|
|
19
25
|
this.mimes = {
|
|
20
26
|
'png': 'image/png',
|
|
21
27
|
'webp': 'image/webp',
|
|
@@ -30,8 +36,13 @@ class Router {
|
|
|
30
36
|
'pdf': 'application/pdf',
|
|
31
37
|
'json': 'application/json'
|
|
32
38
|
};
|
|
39
|
+
this.mimeHeaders = Object.keys(this.mimes).reduce((headers, ext) => {
|
|
40
|
+
headers[ext] = { 'Content-Type': this.mimes[ext] };
|
|
41
|
+
return headers;
|
|
42
|
+
}, {});
|
|
33
43
|
this.compressionMimes = [ 'css', 'js' ];
|
|
34
44
|
this.compressionFiles = [ 'vanilla.min.js', 'app.min.css' ];
|
|
45
|
+
this.enablePrecompressedNegotiation = Boolean(server?.options?.enable_precompressed_negotiation);
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
routeToRegExp(route) {
|
|
@@ -71,7 +82,7 @@ class Router {
|
|
|
71
82
|
|
|
72
83
|
let obj = this;
|
|
73
84
|
let isMatch = false;
|
|
74
|
-
let response = new Response(res);
|
|
85
|
+
let response = new Response(res, obj.server.options);
|
|
75
86
|
let request = new Request(req, {
|
|
76
87
|
onDataReceived: function () {
|
|
77
88
|
if (request.path == '') { request.path = obj.defaultRoute; }
|
|
@@ -93,45 +104,54 @@ class Router {
|
|
|
93
104
|
// -- Check static files
|
|
94
105
|
if (!handled && !isMatch) {
|
|
95
106
|
|
|
96
|
-
let ext = path.extname(
|
|
107
|
+
let ext = path.extname(request.path).replace('.', ''),
|
|
97
108
|
extHandled = false,
|
|
98
109
|
extHeader = {};
|
|
99
110
|
|
|
100
111
|
if (obj.mimes[ext] != undefined && obj.mimes[ext] != 'undefined') {
|
|
101
112
|
extHandled = true;
|
|
102
|
-
extHeader =
|
|
113
|
+
extHeader = obj.mimeHeaders[ext];
|
|
103
114
|
}
|
|
104
115
|
|
|
105
116
|
if (extHandled) {
|
|
106
117
|
|
|
107
|
-
let
|
|
108
|
-
|
|
109
|
-
filename = path.join(rep, route),
|
|
118
|
+
let route = request.path,
|
|
119
|
+
filename = path.join(obj.staticBasePath, route),
|
|
110
120
|
filePrivate = obj.isProtectedFile(route);
|
|
111
121
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
obj.compressionMimes.includes(ext) &&
|
|
115
|
-
obj.compressionFiles.includes(filename.split('/').pop())
|
|
116
|
-
) {
|
|
117
|
-
filename = filename + '.gz';
|
|
118
|
-
extHeader['Content-Encoding'] = 'gzip';
|
|
122
|
+
if (filePrivate) {
|
|
123
|
+
return obj.onNotFound(response);
|
|
119
124
|
}
|
|
120
125
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
let staticCandidates = obj.getStaticCandidates(request, ext, filename);
|
|
127
|
+
let hasConditionalHeaders = Boolean(req.headers['if-none-match'] || req.headers['if-modified-since']);
|
|
128
|
+
obj.resolveFirstAvailableStaticFile(route, request.acceptEncoding, staticCandidates, hasConditionalHeaders, (err, staticFile) => {
|
|
129
|
+
if (err || !staticFile) {
|
|
124
130
|
return obj.onNotFound(response);
|
|
125
131
|
}
|
|
126
|
-
|
|
132
|
+
|
|
133
|
+
let metadata = staticFile.metadata;
|
|
134
|
+
let staticHeaders = obj.buildStaticHeaders(extHeader, staticCandidates, staticFile.contentEncoding, metadata);
|
|
135
|
+
|
|
136
|
+
if (obj.isNotModified(req, metadata)) {
|
|
137
|
+
let notModifiedHeaders = Object.assign({}, staticHeaders);
|
|
138
|
+
delete notModifiedHeaders['Content-Length'];
|
|
139
|
+
res.writeHead(304, notModifiedHeaders);
|
|
140
|
+
return res.end();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const fileStream = fs.createReadStream(staticFile.filename, {
|
|
144
|
+
highWaterMark: obj.staticStreamChunkSize
|
|
145
|
+
});
|
|
127
146
|
fileStream.on('error', (streamErr) => {
|
|
147
|
+
obj.staticMetadataCache.delete(staticFile.filename);
|
|
148
|
+
obj.staticResolutionCache.clear();
|
|
128
149
|
console.error("Error reading file:", streamErr);
|
|
129
150
|
res.writeHead(500);
|
|
130
151
|
res.end('Server Error');
|
|
131
152
|
});
|
|
132
153
|
|
|
133
|
-
|
|
134
|
-
res.writeHead(200, extHeader);
|
|
154
|
+
res.writeHead(200, staticHeaders);
|
|
135
155
|
fileStream.pipe(res);
|
|
136
156
|
res.on('close', () => {});
|
|
137
157
|
});
|
|
@@ -142,8 +162,225 @@ class Router {
|
|
|
142
162
|
isMatch = false;
|
|
143
163
|
}
|
|
144
164
|
|
|
165
|
+
getStaticFileMetadata(filename, forceRefresh, callback) {
|
|
166
|
+
let obj = this;
|
|
167
|
+
forceRefresh = forceRefresh || false;
|
|
168
|
+
let cachedMetadata = obj.staticMetadataCache.get(filename);
|
|
169
|
+
if (cachedMetadata && !forceRefresh) {
|
|
170
|
+
return callback(null, cachedMetadata);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (cachedMetadata && forceRefresh && !obj.shouldRefreshConditionalMetadata(cachedMetadata)) {
|
|
174
|
+
return callback(null, cachedMetadata);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
fs.stat(filename, (err, stats) => {
|
|
178
|
+
if (err || !stats.isFile()) {
|
|
179
|
+
return callback(err || new Error('File not found'));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let metadata = {
|
|
183
|
+
size: stats.size,
|
|
184
|
+
lastModified: stats.mtime.toUTCString(),
|
|
185
|
+
etag: `W/"${stats.size}-${Math.floor(stats.mtimeMs)}"`,
|
|
186
|
+
cachedAt: Date.now()
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
obj.staticMetadataCache.set(filename, metadata);
|
|
190
|
+
obj.watchStaticFile(filename);
|
|
191
|
+
callback(null, metadata);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
getStaticCandidates(request, ext, filename) {
|
|
196
|
+
let obj = this;
|
|
197
|
+
let candidates = [{ filename: filename, contentEncoding: '' }];
|
|
198
|
+
let isCompressible = obj.compressionMimes.includes(ext) && obj.compressionFiles.includes(path.basename(filename));
|
|
199
|
+
if (!isCompressible) {
|
|
200
|
+
return candidates;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let compressedCandidates = [];
|
|
204
|
+
if (obj.enablePrecompressedNegotiation && obj.supportsEncoding(request.acceptEncoding, 'br')) {
|
|
205
|
+
compressedCandidates.push({
|
|
206
|
+
filename: filename + '.br',
|
|
207
|
+
contentEncoding: 'br'
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (obj.supportsEncoding(request.acceptEncoding, 'gzip')) {
|
|
212
|
+
compressedCandidates.push({
|
|
213
|
+
filename: filename + '.gz',
|
|
214
|
+
contentEncoding: 'gzip'
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return compressedCandidates.concat(candidates);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
resolveFirstAvailableStaticFile(route, acceptEncoding, candidates, forceRefresh, callback) {
|
|
222
|
+
let obj = this;
|
|
223
|
+
let resolutionKey = obj.getStaticResolutionKey(route, acceptEncoding);
|
|
224
|
+
let cachedResolution = obj.staticResolutionCache.get(resolutionKey);
|
|
225
|
+
if (cachedResolution) {
|
|
226
|
+
return obj.getStaticFileMetadata(cachedResolution.filename, forceRefresh, (cachedErr, cachedMetadata) => {
|
|
227
|
+
if (!cachedErr && cachedMetadata) {
|
|
228
|
+
return callback(null, {
|
|
229
|
+
filename: cachedResolution.filename,
|
|
230
|
+
contentEncoding: cachedResolution.contentEncoding,
|
|
231
|
+
metadata: cachedMetadata
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
obj.staticResolutionCache.delete(resolutionKey);
|
|
235
|
+
obj.resolveFirstAvailableStaticFile(route, acceptEncoding, candidates, forceRefresh, callback);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let index = 0;
|
|
240
|
+
function resolveCandidate() {
|
|
241
|
+
let currentCandidate = candidates[index];
|
|
242
|
+
if (!currentCandidate) {
|
|
243
|
+
return callback(new Error('No static file found'));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
obj.getStaticFileMetadata(currentCandidate.filename, forceRefresh, (err, metadata) => {
|
|
247
|
+
if (!err && metadata) {
|
|
248
|
+
obj.staticResolutionCache.set(resolutionKey, {
|
|
249
|
+
filename: currentCandidate.filename,
|
|
250
|
+
contentEncoding: currentCandidate.contentEncoding
|
|
251
|
+
});
|
|
252
|
+
return callback(null, {
|
|
253
|
+
filename: currentCandidate.filename,
|
|
254
|
+
contentEncoding: currentCandidate.contentEncoding,
|
|
255
|
+
metadata: metadata
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
index = index + 1;
|
|
259
|
+
resolveCandidate();
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
resolveCandidate();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
getStaticResolutionKey(route, acceptEncoding) {
|
|
267
|
+
let normalizedEncodings = Array.isArray(acceptEncoding) ? acceptEncoding.join(',') : '';
|
|
268
|
+
return `${route}|${normalizedEncodings}`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
shouldRefreshConditionalMetadata(metadata) {
|
|
272
|
+
if (!metadata || !metadata.cachedAt) {
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
return Date.now() - metadata.cachedAt > this.staticMetadataMaxAgeMs;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
buildStaticHeaders(extHeader, candidates, contentEncoding, metadata) {
|
|
279
|
+
let staticHeaders = Object.assign({}, extHeader);
|
|
280
|
+
if (contentEncoding) {
|
|
281
|
+
staticHeaders['Content-Encoding'] = contentEncoding;
|
|
282
|
+
}
|
|
283
|
+
if (candidates.some((candidate) => candidate.contentEncoding)) {
|
|
284
|
+
staticHeaders['Vary'] = 'Accept-Encoding';
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
staticHeaders['Content-Length'] = metadata.size;
|
|
288
|
+
staticHeaders['ETag'] = metadata.etag;
|
|
289
|
+
staticHeaders['Last-Modified'] = metadata.lastModified;
|
|
290
|
+
// Force revalidation to keep clients fresh without hard reload.
|
|
291
|
+
staticHeaders['Cache-Control'] = 'no-cache, must-revalidate';
|
|
292
|
+
return staticHeaders;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
supportsEncoding(acceptEncoding, encoding) {
|
|
296
|
+
if (!Array.isArray(acceptEncoding)) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let normalizedEncoding = String(encoding).toLowerCase();
|
|
301
|
+
return acceptEncoding.some((entry) => {
|
|
302
|
+
if (entry == null) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
let token = String(entry).toLowerCase();
|
|
306
|
+
let parts = token.split(';');
|
|
307
|
+
if (parts[0] !== normalizedEncoding) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let qValue = 1;
|
|
312
|
+
for (let idx = 1; idx < parts.length; idx = idx + 1) {
|
|
313
|
+
let part = parts[idx];
|
|
314
|
+
if (part.startsWith('q=')) {
|
|
315
|
+
let parsedQValue = parseFloat(part.slice(2));
|
|
316
|
+
if (!Number.isNaN(parsedQValue)) {
|
|
317
|
+
qValue = parsedQValue;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return qValue > 0;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
watchStaticFile(filename) {
|
|
327
|
+
let obj = this;
|
|
328
|
+
if (obj.staticFileWatchers.has(filename)) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
let watcher = fs.watch(filename, (eventType) => {
|
|
334
|
+
obj.staticMetadataCache.delete(filename);
|
|
335
|
+
obj.staticResolutionCache.clear();
|
|
336
|
+
if (eventType === 'rename') {
|
|
337
|
+
let renamedWatcher = obj.staticFileWatchers.get(filename);
|
|
338
|
+
if (renamedWatcher) {
|
|
339
|
+
renamedWatcher.close();
|
|
340
|
+
}
|
|
341
|
+
obj.staticFileWatchers.delete(filename);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
watcher.on('error', () => {
|
|
346
|
+
obj.staticMetadataCache.delete(filename);
|
|
347
|
+
obj.staticResolutionCache.clear();
|
|
348
|
+
let activeWatcher = obj.staticFileWatchers.get(filename);
|
|
349
|
+
if (activeWatcher) {
|
|
350
|
+
activeWatcher.close();
|
|
351
|
+
}
|
|
352
|
+
obj.staticFileWatchers.delete(filename);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
obj.staticFileWatchers.set(filename, watcher);
|
|
356
|
+
} catch (err) {
|
|
357
|
+
// If watch cannot be created, keep runtime behavior and continue.
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
isNotModified(req, metadata) {
|
|
362
|
+
let ifNoneMatch = req.headers['if-none-match'];
|
|
363
|
+
if (ifNoneMatch) {
|
|
364
|
+
let etags = ifNoneMatch.split(',').map((etag) => etag.trim());
|
|
365
|
+
if (etags.includes(metadata.etag)) {
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
let ifModifiedSince = req.headers['if-modified-since'];
|
|
371
|
+
if (ifModifiedSince) {
|
|
372
|
+
let requestModifiedSince = new Date(ifModifiedSince).getTime();
|
|
373
|
+
let fileModifiedAt = new Date(metadata.lastModified).getTime();
|
|
374
|
+
if (!Number.isNaN(requestModifiedSince) && requestModifiedSince >= fileModifiedAt) {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
|
|
145
382
|
isProtectedFile(route) {
|
|
146
|
-
let protectedDirs = ['framework', 'external', '
|
|
383
|
+
let protectedDirs = ['framework', 'external', 'node_modules'];
|
|
147
384
|
let routeParts = route.split('/');
|
|
148
385
|
if (routeParts[1] != undefined && routeParts.length > 2) {
|
|
149
386
|
return protectedDirs.includes(routeParts[1]);
|
package/framework/server.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vanilla-jet",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "VannilaJet framework",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"build:qa": "gulp build --env qa",
|
|
13
13
|
"build:staging": "gulp build --env staging",
|
|
14
14
|
"build:prod": "gulp build --env production",
|
|
15
|
-
"test": "
|
|
15
|
+
"test": "node -e \"console.log('No automated tests configured yet')\"",
|
|
16
|
+
"benchmark:static": "node ./scripts/benchmark-static.js"
|
|
16
17
|
},
|
|
17
18
|
"repository": {
|
|
18
19
|
"type": "git",
|