vanilla-jet 1.4.1 → 1.4.3
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/.scripts/generate_packages_json.js +0 -1
- package/CHANGELOG.md +37 -1
- package/README.md +4 -2
- package/ROADMAP_INTEGRAL.md +1 -1
- package/framework/dipper.js +32 -3
- package/framework/response.js +5 -0
- package/framework/router.js +9 -53
- package/framework/server.js +7 -1
- package/gulpfile.js +5 -3
- package/package.json +1 -1
- package/{.grunt → scripts}/compile_html.js +7 -6
- package/.grunt/build_styles_task.js +0 -30
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,38 @@ All notable project changes are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format follows a structure inspired by Keep a Changelog and semantic versioning.
|
|
6
6
|
|
|
7
|
+
## [1.4.3] - 2026-02-19
|
|
8
|
+
|
|
9
|
+
### Removed
|
|
10
|
+
|
|
11
|
+
- Grunt build artifacts: removed `.grunt/` folder, `build_styles_task.js`, and `compile_html.js`.
|
|
12
|
+
- Build pipeline now uses only Gulp; template compilation moved to `scripts/compile_html.js`.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- `gulpfile.js`: `compileTemplates` now invokes `node scripts/compile_html.js` instead of `.grunt/compile_html.js`.
|
|
17
|
+
- `framework/dipper.js`, `.scripts/generate_packages_json.js`: removed `.grunt` references from `processCwd` helpers.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **Reliability under rapid reloads (F5)**: server no longer stops responding after repeated refreshes.
|
|
22
|
+
- Removed `fs.watch` per static file (`staticFileWatchers`); could exhaust resources with prolonged use.
|
|
23
|
+
- Added fallback to `404` for routes without a handled static extension (avoids hanging requests).
|
|
24
|
+
- Destroy file streams when client disconnects (`res.on('close')`) to avoid orphaned streams.
|
|
25
|
+
- Applied same stream cleanup in `response.render()` for HTML template delivery.
|
|
26
|
+
- Added defensive server timeouts: `requestTimeout`, `headersTimeout`, `keepAliveTimeout` (configurable via `settings.profile`).
|
|
27
|
+
|
|
28
|
+
### Compatibility notes
|
|
29
|
+
|
|
30
|
+
- No public API changes.
|
|
31
|
+
- Build output and route behavior unchanged; only internal reliability improvements.
|
|
32
|
+
|
|
33
|
+
## [1.4.2] - 2026-02-19
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- Version bump to 1.4.2.
|
|
38
|
+
|
|
7
39
|
## [1.4.1] - 2026-02-19
|
|
8
40
|
|
|
9
41
|
### Highlights (v1.4.1)
|
|
@@ -11,9 +43,11 @@ The format follows a structure inspired by Keep a Changelog and semantic version
|
|
|
11
43
|
- Completed HU 2.1 (`Fast path de estaticos en Node`).
|
|
12
44
|
- Optimized static serving in `framework/router.js`:
|
|
13
45
|
- Added warm-path static resolution cache (`route + accept-encoding`) to avoid repeated candidate resolution work.
|
|
14
|
-
-
|
|
46
|
+
- Keeps strict conditional metadata revalidation so content changes are visible on reload without stale `304`.
|
|
15
47
|
- Consolidated static header assembly and reused mime header maps.
|
|
16
48
|
- Kept stream-based delivery for large assets and tuned `createReadStream` chunk size.
|
|
49
|
+
- Added asset URL versioning in `framework/dipper.js` (`?v=size-mtime`) for local scripts/styles.
|
|
50
|
+
- Updated `gulp dev` watch flow so JS/CSS recompiles also trigger template compilation and refresh asset URLs in HTML.
|
|
17
51
|
- Added reproducible local benchmark:
|
|
18
52
|
- New script: `npm run benchmark:static`.
|
|
19
53
|
- New guide: `docs/benchmark-static.md`.
|
|
@@ -103,4 +137,6 @@ The format follows a structure inspired by Keep a Changelog and semantic version
|
|
|
103
137
|
[1.3.4]: https://github.com/nalancer08/VanillaJet/releases/tag/v1.3.4
|
|
104
138
|
[1.3.5]: https://github.com/nalancer08/VanillaJet/releases/tag/v1.3.5
|
|
105
139
|
[1.3.6]: https://github.com/nalancer08/VanillaJet/releases/tag/v1.3.6
|
|
140
|
+
[1.4.2]: https://github.com/nalancer08/VanillaJet/releases/tag/v1.4.2
|
|
141
|
+
[1.4.3]: https://github.com/nalancer08/VanillaJet/releases/tag/v1.4.3
|
|
106
142
|
[1.4.1]: https://github.com/nalancer08/VanillaJet/releases/tag/v1.4.1
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Node.js framework for building SPA applications with a JS/CSS/HTML build pipelin
|
|
|
6
6
|
|
|
7
7
|
## Current version
|
|
8
8
|
|
|
9
|
-
- Version: `1.4.
|
|
9
|
+
- Version: `1.4.3`
|
|
10
10
|
- Changelog: see [`CHANGELOG.md`](./CHANGELOG.md)
|
|
11
11
|
- Improvement plan (performance and backward compatibility): see `ROADMAP_INTEGRAL.md`
|
|
12
12
|
|
|
@@ -93,6 +93,8 @@ VanillaJet expects a structure similar to:
|
|
|
93
93
|
|
|
94
94
|
## Build pipeline (summary)
|
|
95
95
|
|
|
96
|
+
Gulp-based pipeline (no Grunt):
|
|
97
|
+
|
|
96
98
|
- Minifies JS and concatenates into `public/scripts/vanilla.min.js`
|
|
97
99
|
- Compiles LESS and generates `public/styles/app.min.css`
|
|
98
100
|
- Compiles templates and generates `public/pages/home.html`
|
|
@@ -125,7 +127,7 @@ Behavior details:
|
|
|
125
127
|
Static serving includes a warm-path optimization focused on Node runtime latency:
|
|
126
128
|
|
|
127
129
|
- Reuses static resolution for repeated requests (`route + accept-encoding`).
|
|
128
|
-
-
|
|
130
|
+
- Keeps conditional revalidation (`ETag`/`Last-Modified`) strict so reload reflects changes immediately.
|
|
129
131
|
- Keeps streaming strategy for large assets (`fs.createReadStream`) with tuned chunk size.
|
|
130
132
|
- Preserves conditional cache behavior (`ETag`/`Last-Modified` + `304`) and precompressed fallback contract.
|
|
131
133
|
|
package/ROADMAP_INTEGRAL.md
CHANGED
|
@@ -117,7 +117,7 @@ Cada historia incluye su ciclo completo: fases, tareas, entregables, metricas, c
|
|
|
117
117
|
|
|
118
118
|
## EPIC 2 - Performance Node + DX de compilacion (foco actual)
|
|
119
119
|
|
|
120
|
-
### HU 2.1 - Fast path de estaticos en Node (completada `v1.4.
|
|
120
|
+
### HU 2.1 - Fast path de estaticos en Node (completada `v1.4.2`)
|
|
121
121
|
|
|
122
122
|
#### Fases
|
|
123
123
|
- F1: profiling de request estatico.
|
package/framework/dipper.js
CHANGED
|
@@ -93,12 +93,12 @@ Dipper.prototype.img = function(filename) {
|
|
|
93
93
|
|
|
94
94
|
Dipper.prototype.script = function(filename) {
|
|
95
95
|
let dir = this.getDir('scripts', false);
|
|
96
|
-
return this.
|
|
96
|
+
return this.versionedUrl(dir + filename);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
Dipper.prototype.style = function(filename) {
|
|
100
100
|
let dir = this.getDir('styles', false);
|
|
101
|
-
return this.
|
|
101
|
+
return this.versionedUrl(dir + filename);;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
Dipper.prototype.pdf = function(filename) {
|
|
@@ -128,6 +128,36 @@ Dipper.prototype.urlTo = function (route) {
|
|
|
128
128
|
return '/' + route.replace(/^\/+/, '');
|
|
129
129
|
};
|
|
130
130
|
|
|
131
|
+
Dipper.prototype.versionedUrl = function(route) {
|
|
132
|
+
const fs = require('fs');
|
|
133
|
+
const path = require('path');
|
|
134
|
+
const normalizedUrl = this.urlTo(route);
|
|
135
|
+
|
|
136
|
+
// External URLs should remain untouched.
|
|
137
|
+
if (/^(?:[a-z][a-z0-9+.-]*:)?\/\//i.test(normalizedUrl)) {
|
|
138
|
+
return normalizedUrl;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const filePath = path.join(this.processCwd(), normalizedUrl.replace(/^\//, ''));
|
|
143
|
+
const stats = fs.statSync(filePath);
|
|
144
|
+
if (!stats.isFile()) {
|
|
145
|
+
return normalizedUrl;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const version = `${stats.size}-${Math.floor(stats.mtimeMs)}`;
|
|
149
|
+
return this.appendQueryParam(normalizedUrl, 'v', version);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
// If file does not exist yet, keep legacy behavior.
|
|
152
|
+
return normalizedUrl;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
Dipper.prototype.appendQueryParam = function(url, key, value) {
|
|
157
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
158
|
+
return `${url}${separator}${key}=${encodeURIComponent(value)}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
131
161
|
Dipper.prototype.registerStyle = function(
|
|
132
162
|
name, url, requires,
|
|
133
163
|
cdn = false, async = false,
|
|
@@ -515,7 +545,6 @@ Dipper.prototype.includeEnvironment = function() {
|
|
|
515
545
|
Dipper.prototype.processCwd = function() {
|
|
516
546
|
|
|
517
547
|
let cwd = process.cwd()
|
|
518
|
-
.replace('/.grunt', '')
|
|
519
548
|
.replace('/.scripts', '')
|
|
520
549
|
.replace('/node_modules', '')
|
|
521
550
|
.replace('/vanilla-jet', '');
|
package/framework/response.js
CHANGED
package/framework/router.js
CHANGED
|
@@ -19,8 +19,6 @@ class Router {
|
|
|
19
19
|
this.staticBasePath = this.cwd.replace('core/framework', '');
|
|
20
20
|
this.staticMetadataCache = new Map();
|
|
21
21
|
this.staticResolutionCache = new Map();
|
|
22
|
-
this.staticFileWatchers = new Map();
|
|
23
|
-
this.staticMetadataMaxAgeMs = 1000;
|
|
24
22
|
this.staticStreamChunkSize = 128 * 1024;
|
|
25
23
|
this.mimes = {
|
|
26
24
|
'png': 'image/png',
|
|
@@ -150,12 +148,18 @@ class Router {
|
|
|
150
148
|
res.writeHead(500);
|
|
151
149
|
res.end('Server Error');
|
|
152
150
|
});
|
|
151
|
+
res.on('close', () => {
|
|
152
|
+
if (!res.writableEnded) {
|
|
153
|
+
fileStream.destroy();
|
|
154
|
+
}
|
|
155
|
+
});
|
|
153
156
|
|
|
154
157
|
res.writeHead(200, staticHeaders);
|
|
155
158
|
fileStream.pipe(res);
|
|
156
|
-
res.on('close', () => {});
|
|
157
159
|
});
|
|
158
|
-
}
|
|
160
|
+
} else {
|
|
161
|
+
return obj.onNotFound(response);
|
|
162
|
+
}
|
|
159
163
|
}
|
|
160
164
|
}
|
|
161
165
|
}), handled = false;
|
|
@@ -170,10 +174,6 @@ class Router {
|
|
|
170
174
|
return callback(null, cachedMetadata);
|
|
171
175
|
}
|
|
172
176
|
|
|
173
|
-
if (cachedMetadata && forceRefresh && !obj.shouldRefreshConditionalMetadata(cachedMetadata)) {
|
|
174
|
-
return callback(null, cachedMetadata);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
177
|
fs.stat(filename, (err, stats) => {
|
|
178
178
|
if (err || !stats.isFile()) {
|
|
179
179
|
return callback(err || new Error('File not found'));
|
|
@@ -182,12 +182,10 @@ class Router {
|
|
|
182
182
|
let metadata = {
|
|
183
183
|
size: stats.size,
|
|
184
184
|
lastModified: stats.mtime.toUTCString(),
|
|
185
|
-
etag: `W/"${stats.size}-${Math.floor(stats.mtimeMs)}"
|
|
186
|
-
cachedAt: Date.now()
|
|
185
|
+
etag: `W/"${stats.size}-${Math.floor(stats.mtimeMs)}"`
|
|
187
186
|
};
|
|
188
187
|
|
|
189
188
|
obj.staticMetadataCache.set(filename, metadata);
|
|
190
|
-
obj.watchStaticFile(filename);
|
|
191
189
|
callback(null, metadata);
|
|
192
190
|
});
|
|
193
191
|
}
|
|
@@ -268,13 +266,6 @@ class Router {
|
|
|
268
266
|
return `${route}|${normalizedEncodings}`;
|
|
269
267
|
}
|
|
270
268
|
|
|
271
|
-
shouldRefreshConditionalMetadata(metadata) {
|
|
272
|
-
if (!metadata || !metadata.cachedAt) {
|
|
273
|
-
return true;
|
|
274
|
-
}
|
|
275
|
-
return Date.now() - metadata.cachedAt > this.staticMetadataMaxAgeMs;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
269
|
buildStaticHeaders(extHeader, candidates, contentEncoding, metadata) {
|
|
279
270
|
let staticHeaders = Object.assign({}, extHeader);
|
|
280
271
|
if (contentEncoding) {
|
|
@@ -323,41 +314,6 @@ class Router {
|
|
|
323
314
|
});
|
|
324
315
|
}
|
|
325
316
|
|
|
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
317
|
isNotModified(req, metadata) {
|
|
362
318
|
let ifNoneMatch = req.headers['if-none-match'];
|
|
363
319
|
if (ifNoneMatch) {
|
package/framework/server.js
CHANGED
|
@@ -32,7 +32,10 @@ class Server {
|
|
|
32
32
|
_.defaults(opts, {
|
|
33
33
|
https_server: false,
|
|
34
34
|
wsServer: false,
|
|
35
|
-
enable_precompressed_negotiation: false
|
|
35
|
+
enable_precompressed_negotiation: false,
|
|
36
|
+
request_timeout_ms: 30000,
|
|
37
|
+
headers_timeout_ms: 35000,
|
|
38
|
+
keep_alive_timeout_ms: 5000
|
|
36
39
|
});
|
|
37
40
|
obj.options = opts;
|
|
38
41
|
|
|
@@ -91,6 +94,9 @@ class Server {
|
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
// -- Set the port
|
|
97
|
+
obj.httpx.requestTimeout = Number(obj.options.request_timeout_ms) || 30000;
|
|
98
|
+
obj.httpx.headersTimeout = Number(obj.options.headers_timeout_ms) || 35000;
|
|
99
|
+
obj.httpx.keepAliveTimeout = Number(obj.options.keep_alive_timeout_ms) || 5000;
|
|
94
100
|
obj.httpx.listen(obj.options.port || 8080);
|
|
95
101
|
}
|
|
96
102
|
|
package/gulpfile.js
CHANGED
|
@@ -113,7 +113,7 @@ function compressCss() {
|
|
|
113
113
|
// Template compilation
|
|
114
114
|
function compileTemplates() {
|
|
115
115
|
return gulp.src('.')
|
|
116
|
-
.pipe(shell([`node
|
|
116
|
+
.pipe(shell([`node scripts/compile_html.js`]));
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
// Watch task
|
|
@@ -123,7 +123,8 @@ function watchFiles(cb) {
|
|
|
123
123
|
// Watch LESS files
|
|
124
124
|
watch([`${base}/assets/styles/less/**/*.less`], gulp.series(
|
|
125
125
|
buildLess,
|
|
126
|
-
compressCss
|
|
126
|
+
compressCss,
|
|
127
|
+
compileTemplates
|
|
127
128
|
));
|
|
128
129
|
|
|
129
130
|
// Watch HTML files
|
|
@@ -138,7 +139,8 @@ function watchFiles(cb) {
|
|
|
138
139
|
uglifyJs,
|
|
139
140
|
concatJs,
|
|
140
141
|
cleanMinified,
|
|
141
|
-
compressJs
|
|
142
|
+
compressJs,
|
|
143
|
+
compileTemplates
|
|
142
144
|
));
|
|
143
145
|
|
|
144
146
|
cb();
|
package/package.json
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
const path = require("path"),
|
|
3
3
|
fs = require("fs"),
|
|
4
4
|
nunjucks = require('nunjucks'),
|
|
5
|
-
identifier = 'templates',
|
|
6
5
|
chalk = require('chalk'),
|
|
7
6
|
zlib = require('zlib');
|
|
8
7
|
|
|
@@ -43,9 +42,11 @@ function main() {
|
|
|
43
42
|
// -- Get home.html
|
|
44
43
|
let homePageName = 'home.html';
|
|
45
44
|
getHtmlFromPage(homePageName).then((htmlContent) => {
|
|
46
|
-
if (htmlContent) {
|
|
45
|
+
if (htmlContent) {
|
|
46
|
+
// -- Compile the htmlContent
|
|
47
|
+
const compiledHtmlContent = compileTemplate(htmlContent);
|
|
47
48
|
// -- Divide content line by line
|
|
48
|
-
const htmlContentLines =
|
|
49
|
+
const htmlContentLines = compiledHtmlContent.split('\n');
|
|
49
50
|
let lines = Array.from(htmlContentLines);
|
|
50
51
|
// -- Iterate over each line
|
|
51
52
|
for (let line of htmlContentLines) {
|
|
@@ -58,7 +59,7 @@ function main() {
|
|
|
58
59
|
// -- Get template name
|
|
59
60
|
var templateName = line.replace('include::', '');
|
|
60
61
|
// -- Check if its name "templates" add all templates if not add specific one
|
|
61
|
-
if (templateName ===
|
|
62
|
+
if (templateName === 'templates') {
|
|
62
63
|
|
|
63
64
|
let allTemplatesCompiled = '';
|
|
64
65
|
for (let templateName in templates) {
|
|
@@ -211,7 +212,7 @@ function cleanALine(line) {
|
|
|
211
212
|
|
|
212
213
|
function processCwd() {
|
|
213
214
|
return process.cwd()
|
|
214
|
-
.replace('
|
|
215
|
+
.replace('/scripts', '')
|
|
215
216
|
.replace('/gulp', '')
|
|
216
217
|
.replace('/node_modules/vanilla-jet', '');
|
|
217
|
-
}
|
|
218
|
+
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
module.exports = function(grunt) {
|
|
2
|
-
grunt.registerTask('buildLess', 'Compile admin.less and add .section.less', function() {
|
|
3
|
-
|
|
4
|
-
// -- Functions
|
|
5
|
-
function getCleanedCWD() {
|
|
6
|
-
const cwd = process.cwd();
|
|
7
|
-
return cwd
|
|
8
|
-
.replace('/node_modules', '')
|
|
9
|
-
.replace('/vanilla-jet', '')
|
|
10
|
-
.replace('/.grunt', '');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// -- Content
|
|
14
|
-
let adminContent = grunt.file.read(`${getCleanedCWD()}/assets/styles/less/admin.less`);
|
|
15
|
-
let sectionFiles = grunt.file.expand([
|
|
16
|
-
`${getCleanedCWD()}/assets/styles/less/sections/**/*.section.less`,
|
|
17
|
-
`${getCleanedCWD()}/assets/styles/less/sections/*.section.less`
|
|
18
|
-
]);
|
|
19
|
-
|
|
20
|
-
let combinedContent = adminContent + '\n';
|
|
21
|
-
sectionFiles.forEach(function(filePath) {
|
|
22
|
-
let sectionContent = grunt.file.read(filePath);
|
|
23
|
-
combinedContent += `\n/* ${filePath} */\n` + sectionContent;
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
// -- New file
|
|
27
|
-
grunt.file.write(`${getCleanedCWD()}/assets/styles/less/admin_build.less`, combinedContent);
|
|
28
|
-
grunt.task.run(['less']);
|
|
29
|
-
});
|
|
30
|
-
};
|