vanilla-jet 1.3.1 → 1.4.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/.github/workflows/deploy.yml +18 -11
- package/.grunt/compile_html.js +1 -10
- package/.scripts/run_vite.js +36 -0
- package/CHANGELOG.md +95 -0
- package/README.md +143 -5
- package/ROADMAP_INTEGRAL.md +248 -0
- package/bin.js +35 -35
- 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 +198 -16
- package/framework/server.js +3 -2
- package/gulpfile.js +1 -8
- package/package.json +15 -12
- package/vite.config.js +184 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
version: "3.9"
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
app:
|
|
5
|
+
build:
|
|
6
|
+
context: .
|
|
7
|
+
dockerfile: docs/deployment/Dockerfile.app.example
|
|
8
|
+
container_name: vanillajet-app
|
|
9
|
+
environment:
|
|
10
|
+
- PORT=8080
|
|
11
|
+
expose:
|
|
12
|
+
- "8080"
|
|
13
|
+
|
|
14
|
+
nginx:
|
|
15
|
+
image: nginx:1.27-alpine
|
|
16
|
+
container_name: vanillajet-nginx
|
|
17
|
+
depends_on:
|
|
18
|
+
- app
|
|
19
|
+
ports:
|
|
20
|
+
- "80:80"
|
|
21
|
+
volumes:
|
|
22
|
+
- ./public:/app/public:ro
|
|
23
|
+
- ./docs/deployment/nginx.default.conf.example:/etc/nginx/conf.d/default.conf:ro
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
server {
|
|
2
|
+
listen 80;
|
|
3
|
+
server_name _;
|
|
4
|
+
|
|
5
|
+
# Build output served directly by nginx
|
|
6
|
+
root /app/public;
|
|
7
|
+
index pages/home.html;
|
|
8
|
+
|
|
9
|
+
# API and dynamic routes handled by VanillaJet backend
|
|
10
|
+
location /api/ {
|
|
11
|
+
proxy_pass http://app:8080;
|
|
12
|
+
proxy_http_version 1.1;
|
|
13
|
+
proxy_set_header Host $host;
|
|
14
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
15
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
16
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
# Static assets with long cache
|
|
20
|
+
location ~* \.(?:css|js|png|jpg|jpeg|gif|svg|webp|ico|ttf|otf|woff|woff2)$ {
|
|
21
|
+
try_files $uri =404;
|
|
22
|
+
expires 30d;
|
|
23
|
+
add_header Cache-Control "public, max-age=2592000, immutable";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# HTML should revalidate frequently
|
|
27
|
+
location ~* \.html$ {
|
|
28
|
+
try_files $uri =404;
|
|
29
|
+
add_header Cache-Control "no-cache, must-revalidate";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# SPA fallback to main page
|
|
33
|
+
location / {
|
|
34
|
+
try_files $uri $uri/ /pages/home.html;
|
|
35
|
+
}
|
|
36
|
+
}
|
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,8 @@ class Router {
|
|
|
16
16
|
this.defaultRoute = '';
|
|
17
17
|
this.server = server;
|
|
18
18
|
this.cwd = process.cwd();
|
|
19
|
+
this.staticMetadataCache = new Map();
|
|
20
|
+
this.staticFileWatchers = new Map();
|
|
19
21
|
this.mimes = {
|
|
20
22
|
'png': 'image/png',
|
|
21
23
|
'webp': 'image/webp',
|
|
@@ -32,6 +34,7 @@ class Router {
|
|
|
32
34
|
};
|
|
33
35
|
this.compressionMimes = [ 'css', 'js' ];
|
|
34
36
|
this.compressionFiles = [ 'vanilla.min.js', 'app.min.css' ];
|
|
37
|
+
this.enablePrecompressedNegotiation = Boolean(server?.options?.enable_precompressed_negotiation);
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
routeToRegExp(route) {
|
|
@@ -71,7 +74,7 @@ class Router {
|
|
|
71
74
|
|
|
72
75
|
let obj = this;
|
|
73
76
|
let isMatch = false;
|
|
74
|
-
let response = new Response(res);
|
|
77
|
+
let response = new Response(res, obj.server.options);
|
|
75
78
|
let request = new Request(req, {
|
|
76
79
|
onDataReceived: function () {
|
|
77
80
|
if (request.path == '') { request.path = obj.defaultRoute; }
|
|
@@ -93,7 +96,7 @@ class Router {
|
|
|
93
96
|
// -- Check static files
|
|
94
97
|
if (!handled && !isMatch) {
|
|
95
98
|
|
|
96
|
-
let ext = path.extname(
|
|
99
|
+
let ext = path.extname(request.path).replace('.', ''),
|
|
97
100
|
extHandled = false,
|
|
98
101
|
extHeader = {};
|
|
99
102
|
|
|
@@ -109,29 +112,47 @@ class Router {
|
|
|
109
112
|
filename = path.join(rep, route),
|
|
110
113
|
filePrivate = obj.isProtectedFile(route);
|
|
111
114
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
obj.compressionMimes.includes(ext) &&
|
|
115
|
-
obj.compressionFiles.includes(filename.split('/').pop())
|
|
116
|
-
) {
|
|
117
|
-
filename = filename + '.gz';
|
|
118
|
-
extHeader['Content-Encoding'] = 'gzip';
|
|
115
|
+
if (filePrivate) {
|
|
116
|
+
return obj.onNotFound(response);
|
|
119
117
|
}
|
|
120
118
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
let staticCandidates = obj.getStaticCandidates(request, ext, filename);
|
|
120
|
+
let hasConditionalHeaders = Boolean(req.headers['if-none-match'] || req.headers['if-modified-since']);
|
|
121
|
+
obj.resolveFirstAvailableStaticFile(staticCandidates, hasConditionalHeaders, (err, staticFile) => {
|
|
122
|
+
if (err || !staticFile) {
|
|
124
123
|
return obj.onNotFound(response);
|
|
125
124
|
}
|
|
126
|
-
|
|
125
|
+
|
|
126
|
+
let staticHeaders = Object.assign({}, extHeader);
|
|
127
|
+
if (staticFile.contentEncoding) {
|
|
128
|
+
staticHeaders['Content-Encoding'] = staticFile.contentEncoding;
|
|
129
|
+
}
|
|
130
|
+
if (staticCandidates.some((candidate) => candidate.contentEncoding)) {
|
|
131
|
+
staticHeaders['Vary'] = 'Accept-Encoding';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let metadata = staticFile.metadata;
|
|
135
|
+
staticHeaders['Content-Length'] = metadata.size;
|
|
136
|
+
staticHeaders['ETag'] = metadata.etag;
|
|
137
|
+
staticHeaders['Last-Modified'] = metadata.lastModified;
|
|
138
|
+
// Force revalidation to keep clients fresh without hard reload.
|
|
139
|
+
staticHeaders['Cache-Control'] = 'no-cache, must-revalidate';
|
|
140
|
+
|
|
141
|
+
if (obj.isNotModified(req, metadata)) {
|
|
142
|
+
let notModifiedHeaders = Object.assign({}, staticHeaders);
|
|
143
|
+
delete notModifiedHeaders['Content-Length'];
|
|
144
|
+
res.writeHead(304, notModifiedHeaders);
|
|
145
|
+
return res.end();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const fileStream = fs.createReadStream(staticFile.filename);
|
|
127
149
|
fileStream.on('error', (streamErr) => {
|
|
128
150
|
console.error("Error reading file:", streamErr);
|
|
129
151
|
res.writeHead(500);
|
|
130
152
|
res.end('Server Error');
|
|
131
153
|
});
|
|
132
154
|
|
|
133
|
-
|
|
134
|
-
res.writeHead(200, extHeader);
|
|
155
|
+
res.writeHead(200, staticHeaders);
|
|
135
156
|
fileStream.pipe(res);
|
|
136
157
|
res.on('close', () => {});
|
|
137
158
|
});
|
|
@@ -142,8 +163,169 @@ class Router {
|
|
|
142
163
|
isMatch = false;
|
|
143
164
|
}
|
|
144
165
|
|
|
166
|
+
getStaticFileMetadata(filename, forceRefresh, callback) {
|
|
167
|
+
let obj = this;
|
|
168
|
+
forceRefresh = forceRefresh || false;
|
|
169
|
+
let cachedMetadata = obj.staticMetadataCache.get(filename);
|
|
170
|
+
if (cachedMetadata && !forceRefresh) {
|
|
171
|
+
return callback(null, cachedMetadata);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
fs.stat(filename, (err, stats) => {
|
|
175
|
+
if (err || !stats.isFile()) {
|
|
176
|
+
return callback(err || new Error('File not found'));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let metadata = {
|
|
180
|
+
size: stats.size,
|
|
181
|
+
lastModified: stats.mtime.toUTCString(),
|
|
182
|
+
etag: `W/"${stats.size}-${Math.floor(stats.mtimeMs)}"`
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
obj.staticMetadataCache.set(filename, metadata);
|
|
186
|
+
obj.watchStaticFile(filename);
|
|
187
|
+
callback(null, metadata);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
getStaticCandidates(request, ext, filename) {
|
|
192
|
+
let obj = this;
|
|
193
|
+
let candidates = [{ filename: filename, contentEncoding: '' }];
|
|
194
|
+
let isCompressible = obj.compressionMimes.includes(ext) && obj.compressionFiles.includes(path.basename(filename));
|
|
195
|
+
if (!isCompressible) {
|
|
196
|
+
return candidates;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let compressedCandidates = [];
|
|
200
|
+
if (obj.enablePrecompressedNegotiation && obj.supportsEncoding(request.acceptEncoding, 'br')) {
|
|
201
|
+
compressedCandidates.push({
|
|
202
|
+
filename: filename + '.br',
|
|
203
|
+
contentEncoding: 'br'
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (obj.supportsEncoding(request.acceptEncoding, 'gzip')) {
|
|
208
|
+
compressedCandidates.push({
|
|
209
|
+
filename: filename + '.gz',
|
|
210
|
+
contentEncoding: 'gzip'
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return compressedCandidates.concat(candidates);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
resolveFirstAvailableStaticFile(candidates, forceRefresh, callback) {
|
|
218
|
+
let obj = this;
|
|
219
|
+
let index = 0;
|
|
220
|
+
function resolveCandidate() {
|
|
221
|
+
let currentCandidate = candidates[index];
|
|
222
|
+
if (!currentCandidate) {
|
|
223
|
+
return callback(new Error('No static file found'));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
obj.getStaticFileMetadata(currentCandidate.filename, forceRefresh, (err, metadata) => {
|
|
227
|
+
if (!err && metadata) {
|
|
228
|
+
return callback(null, {
|
|
229
|
+
filename: currentCandidate.filename,
|
|
230
|
+
contentEncoding: currentCandidate.contentEncoding,
|
|
231
|
+
metadata: metadata
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
index = index + 1;
|
|
235
|
+
resolveCandidate();
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
resolveCandidate();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
supportsEncoding(acceptEncoding, encoding) {
|
|
243
|
+
if (!Array.isArray(acceptEncoding)) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let normalizedEncoding = String(encoding).toLowerCase();
|
|
248
|
+
return acceptEncoding.some((entry) => {
|
|
249
|
+
if (entry == null) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
let token = String(entry).toLowerCase();
|
|
253
|
+
let parts = token.split(';');
|
|
254
|
+
if (parts[0] !== normalizedEncoding) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let qValue = 1;
|
|
259
|
+
for (let idx = 1; idx < parts.length; idx = idx + 1) {
|
|
260
|
+
let part = parts[idx];
|
|
261
|
+
if (part.startsWith('q=')) {
|
|
262
|
+
let parsedQValue = parseFloat(part.slice(2));
|
|
263
|
+
if (!Number.isNaN(parsedQValue)) {
|
|
264
|
+
qValue = parsedQValue;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return qValue > 0;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
watchStaticFile(filename) {
|
|
274
|
+
let obj = this;
|
|
275
|
+
if (obj.staticFileWatchers.has(filename)) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
let watcher = fs.watch(filename, (eventType) => {
|
|
281
|
+
obj.staticMetadataCache.delete(filename);
|
|
282
|
+
if (eventType === 'rename') {
|
|
283
|
+
let renamedWatcher = obj.staticFileWatchers.get(filename);
|
|
284
|
+
if (renamedWatcher) {
|
|
285
|
+
renamedWatcher.close();
|
|
286
|
+
}
|
|
287
|
+
obj.staticFileWatchers.delete(filename);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
watcher.on('error', () => {
|
|
292
|
+
obj.staticMetadataCache.delete(filename);
|
|
293
|
+
let activeWatcher = obj.staticFileWatchers.get(filename);
|
|
294
|
+
if (activeWatcher) {
|
|
295
|
+
activeWatcher.close();
|
|
296
|
+
}
|
|
297
|
+
obj.staticFileWatchers.delete(filename);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
obj.staticFileWatchers.set(filename, watcher);
|
|
301
|
+
} catch (err) {
|
|
302
|
+
// If watch cannot be created, keep runtime behavior and continue.
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
isNotModified(req, metadata) {
|
|
307
|
+
let ifNoneMatch = req.headers['if-none-match'];
|
|
308
|
+
if (ifNoneMatch) {
|
|
309
|
+
let etags = ifNoneMatch.split(',').map((etag) => etag.trim());
|
|
310
|
+
if (etags.includes(metadata.etag)) {
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
let ifModifiedSince = req.headers['if-modified-since'];
|
|
316
|
+
if (ifModifiedSince) {
|
|
317
|
+
let requestModifiedSince = new Date(ifModifiedSince).getTime();
|
|
318
|
+
let fileModifiedAt = new Date(metadata.lastModified).getTime();
|
|
319
|
+
if (!Number.isNaN(requestModifiedSince) && requestModifiedSince >= fileModifiedAt) {
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
|
|
145
327
|
isProtectedFile(route) {
|
|
146
|
-
let protectedDirs = ['framework', 'external', '
|
|
328
|
+
let protectedDirs = ['framework', 'external', 'node_modules'];
|
|
147
329
|
let routeParts = route.split('/');
|
|
148
330
|
if (routeParts[1] != undefined && routeParts.length > 2) {
|
|
149
331
|
return protectedDirs.includes(routeParts[1]);
|
package/framework/server.js
CHANGED
|
@@ -25,13 +25,14 @@ class Server {
|
|
|
25
25
|
|
|
26
26
|
let obj = this,
|
|
27
27
|
settings = options.settings,
|
|
28
|
-
opts = settings[
|
|
28
|
+
opts = settings['profile'] || {},
|
|
29
29
|
shared = settings['shared'] || {},
|
|
30
30
|
security = settings['security'] || {};
|
|
31
31
|
|
|
32
32
|
_.defaults(opts, {
|
|
33
33
|
https_server: false,
|
|
34
|
-
wsServer: false
|
|
34
|
+
wsServer: false,
|
|
35
|
+
enable_precompressed_negotiation: false
|
|
35
36
|
});
|
|
36
37
|
obj.options = opts;
|
|
37
38
|
|
package/gulpfile.js
CHANGED
|
@@ -13,13 +13,6 @@ const del = require('del');
|
|
|
13
13
|
const gulpif = require('gulp-if');
|
|
14
14
|
const minimist = require('minimist');
|
|
15
15
|
|
|
16
|
-
// Parse command line arguments
|
|
17
|
-
const knownOptions = {
|
|
18
|
-
string: 'env',
|
|
19
|
-
default: { env: process.env.NODE_ENV || 'development' }
|
|
20
|
-
};
|
|
21
|
-
const options = minimist(process.argv.slice(2), knownOptions);
|
|
22
|
-
|
|
23
16
|
// Helper functions
|
|
24
17
|
function getCwd() {
|
|
25
18
|
return process.cwd()
|
|
@@ -120,7 +113,7 @@ function compressCss() {
|
|
|
120
113
|
// Template compilation
|
|
121
114
|
function compileTemplates() {
|
|
122
115
|
return gulp.src('.')
|
|
123
|
-
.pipe(shell([`node .grunt/compile_html.js
|
|
116
|
+
.pipe(shell([`node .grunt/compile_html.js`]));
|
|
124
117
|
}
|
|
125
118
|
|
|
126
119
|
// Watch task
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vanilla-jet",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "VannilaJet framework",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,10 +9,12 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"setup": "node ./.scripts/generate_packages_json.js",
|
|
11
11
|
"dev": "gulp dev --env development",
|
|
12
|
+
"dev:vite": "node ./.scripts/run_vite.js dev",
|
|
13
|
+
"build:vite": "node ./.scripts/run_vite.js build",
|
|
12
14
|
"build:qa": "gulp build --env qa",
|
|
13
15
|
"build:staging": "gulp build --env staging",
|
|
14
16
|
"build:prod": "gulp build --env production",
|
|
15
|
-
"test": "
|
|
17
|
+
"test": "node -e \"console.log('No automated tests configured yet')\""
|
|
16
18
|
},
|
|
17
19
|
"repository": {
|
|
18
20
|
"type": "git",
|
|
@@ -31,15 +33,6 @@
|
|
|
31
33
|
"dependencies": {
|
|
32
34
|
"blueimp-md5": "2.19.0",
|
|
33
35
|
"chalk": "4.1.2",
|
|
34
|
-
"html-minifier-terser": "7.2.0",
|
|
35
|
-
"js-beautify": "1.15.4",
|
|
36
|
-
"jsrsasign": "11.1.0",
|
|
37
|
-
"jwt-simple": "0.5.6",
|
|
38
|
-
"minimist": "1.2.8",
|
|
39
|
-
"nodemon": "3.1.10",
|
|
40
|
-
"nunjucks": "3.2.4",
|
|
41
|
-
"underscore": ">= 1.12.x",
|
|
42
|
-
"zlib": "1.0.5",
|
|
43
36
|
"del": "^6.0.0",
|
|
44
37
|
"gulp": "^4.0.2",
|
|
45
38
|
"gulp-clean-css": "^4.3.0",
|
|
@@ -52,6 +45,16 @@
|
|
|
52
45
|
"gulp-rename": "^2.0.0",
|
|
53
46
|
"gulp-shell": "^0.8.0",
|
|
54
47
|
"gulp-uglify": "^3.0.2",
|
|
55
|
-
"gulp-watch": "^5.0.1"
|
|
48
|
+
"gulp-watch": "^5.0.1",
|
|
49
|
+
"html-minifier-terser": "7.2.0",
|
|
50
|
+
"js-beautify": "1.15.4",
|
|
51
|
+
"jsrsasign": "11.1.0",
|
|
52
|
+
"jwt-simple": "0.5.6",
|
|
53
|
+
"minimist": "1.2.8",
|
|
54
|
+
"nodemon": "3.1.10",
|
|
55
|
+
"nunjucks": "3.2.4",
|
|
56
|
+
"underscore": ">= 1.12.x",
|
|
57
|
+
"vite": "^7.3.1",
|
|
58
|
+
"zlib": "1.0.5"
|
|
56
59
|
}
|
|
57
60
|
}
|