Updated JAEL

This commit is contained in:
thomashii 2021-12-25 10:40:30 +08:00
parent d8336d26b5
commit 127b14bd99
6 changed files with 107 additions and 32 deletions

View file

@ -1,5 +1,9 @@
# Change Log
## 4.3.0
* Added `jaelTemplatePreload` to preload all JAEL templates into a cache
## 4.2.3
* Turned on generated HTML minification by default

View file

@ -77,3 +77,23 @@ void main() async {
```
To apply additional transforms to parsed documents, provide a set of `patch` functions, like in `package:jael3_preprocessor`.
## Performance Optimization
For handling large volume of initial requests, consider using `jaelTemplatePreload` to preload all the JAEL templates
into an external cache.
```dart
var templateDir = fileSystem.directory('views');
// Preload JAEL view templates into cache
var viewCache = <String, Document>{};
jaelTemplatePreload(templateDir, viewCache);
// Inject cache into JAEL renderer
await app.configure(
jael(fileSystem.directory('views'), cache: viewCache),
);
```

View file

@ -3,6 +3,7 @@ import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_framework/http.dart';
import 'package:angel3_jael/angel3_jael.dart';
import 'package:file/local.dart';
import 'package:jael3/jael3.dart';
import 'package:logging/logging.dart';
main() async {

View file

@ -17,11 +17,12 @@ AngelConfigurer jael(Directory viewsDirectory,
{String fileExtension = '.jael',
bool strictResolution = false,
bool cacheViews = true,
Map<String, Document>? cache,
Iterable<Patcher> patch = const [],
bool asDSX = false,
bool minified = true,
CodeBuffer Function()? createBuffer}) {
var cache = <String, Document>{};
var _cache = cache ?? <String, Document>{};
var bufferFunc = createBuffer ?? () => CodeBuffer();
@ -34,31 +35,20 @@ AngelConfigurer jael(Directory viewsDirectory,
var errors = <JaelError>[];
Document? processed;
if (cacheViews && cache.containsKey(name)) {
processed = cache[name];
} else {
var file = viewsDirectory.childFile(name + fileExtension);
var contents = await file.readAsString();
var doc = parseDocument(contents,
sourceUrl: file.uri, asDSX: asDSX, onError: errors.add);
if (doc == null) {
throw ArgumentError(name + fileExtension + " does not exists");
}
//var stopwatch = Stopwatch()..start();
try {
processed = await (resolve(doc, viewsDirectory,
patch: patch, onError: errors.add));
} catch (e) {
// Ignore these errors, so that we can show syntax errors.
}
if (processed == null) {
throw ArgumentError(name + fileExtension + " does not exists");
}
if (cacheViews && _cache.containsKey(name)) {
processed = _cache[name];
} else {
processed = await _loadViewTemplate(viewsDirectory, name,
fileExtension: fileExtension, asDSX: asDSX, patch: patch);
if (cacheViews) {
cache[name] = processed;
_cache[name] = processed!;
}
}
//print('Time executed: ${stopwatch.elapsed.inMilliseconds}');
//stopwatch.stop();
var buf = bufferFunc();
var scope = SymbolTable(
@ -69,7 +59,7 @@ AngelConfigurer jael(Directory viewsDirectory,
if (errors.isEmpty) {
try {
const Renderer().render(processed!, buf, scope,
strictResolution: strictResolution == true);
strictResolution: strictResolution);
return buf.toString();
} on JaelError catch (e) {
errors.add(e);
@ -81,3 +71,54 @@ AngelConfigurer jael(Directory viewsDirectory,
};
};
}
/// Preload all of Jael templates into a cache
///
///
/// To apply additional transforms to parsed documents, provide a set of [patch] functions.
Future<void> jaelTemplatePreload(
Directory viewsDirectory, Map<String, Document> cache,
{String fileExtension = '.jael',
bool asDSX = false,
Iterable<Patcher> patch = const []}) async {
await viewsDirectory.list(recursive: true).forEach((f) async {
if (f.basename.endsWith(fileExtension)) {
var name = f.basename.split(".");
if (name.length > 1) {
print("View: ${name[0]}");
Document? processed = await _loadViewTemplate(viewsDirectory, name[0]);
if (processed != null) {
cache[name[0]] = processed;
}
}
}
});
}
Future<Document?> _loadViewTemplate(Directory viewsDirectory, String name,
{String fileExtension = '.jael',
bool asDSX = false,
Iterable<Patcher> patch = const []}) async {
var errors = <JaelError>[];
Document? processed;
var file = viewsDirectory.childFile(name + fileExtension);
var contents = await file.readAsString();
var doc = parseDocument(contents,
sourceUrl: file.uri, asDSX: asDSX, onError: errors.add);
if (doc == null) {
throw ArgumentError(file.basename + " does not exists");
}
try {
processed =
await (resolve(doc, viewsDirectory, patch: patch, onError: errors.add));
} catch (e) {
// Ignore these errors, so that we can show syntax errors.
}
if (processed == null) {
throw ArgumentError(file.basename + " does not exists");
}
return processed;
}

View file

@ -1,5 +1,5 @@
name: angel3_jael
version: 4.2.3
version: 4.3.0
description: Angel support for the Jael templating engine, similar to Blade or Liquid.
homepage: https://angel3-framework.web.app/
repository: https://github.com/dukefirehawk/angel/tree/master/packages/jael/angel_jael
@ -13,6 +13,7 @@ dependencies:
jael3_preprocessor: ^4.2.0
file: ^6.0.0
logging: ^1.0.1
dev_dependencies:
angel3_test: ^4.0.0
html: ^0.15.0

View file

@ -3,6 +3,7 @@ import 'package:angel3_jael/angel3_jael.dart';
import 'package:angel3_test/angel3_test.dart';
import 'package:file/memory.dart';
import 'package:html/parser.dart' as html;
import 'package:jael3/jael3.dart';
import 'package:logging/logging.dart';
import 'package:test/test.dart';
@ -40,13 +41,17 @@ void main() {
</extend>
''');
app.get('/github/:username', (req, res) {
app.get('/github/:username', (req, res) async {
var username = req.params['username'];
return res.render('github', {'username': username});
});
//Preload the view template
var viewCache = <String, Document>{};
jaelTemplatePreload(viewsDirectory, viewCache);
await app.configure(
jael(viewsDirectory, cacheViews: true),
jael(viewsDirectory, cache: viewCache),
);
app.fallback((req, res) => throw AngelHttpException.notFound());
@ -73,16 +78,19 @@ void main() {
.outerHtml);
});
test('can render multiples', () async {
// Load the view template and wait for it to be cached
var response1 = await client.get(Uri.parse('/github/thosakwe'));
for (var i = 0; i < 100; i++) {
test('initial load concurreny', () async {
// Concurrently hit the same JAEL page
for (var i = 0; i < 512; i++) {
client.get(Uri.parse('/github/thosakwe'));
}
var response = await client.get(Uri.parse('/github/thosakwe'));
//print('Body:\n${response.body}');
Stopwatch stopwatch = Stopwatch()..start();
var response = await client.get(Uri.parse('/github/thosakwe'));
var elapsedTime = stopwatch.elapsed.inMilliseconds;
print('Latency is $elapsedTime');
print('Body:\n${response.body}');
expect(
html.parse(response.body).outerHtml,
html