Merge pull request #33 from dukefirehawk/feature/update-jael

Added JAEL template preload
This commit is contained in:
Thomas Hii 2021-12-25 10:43:24 +08:00 committed by GitHub
commit 97fb2edf87
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 107 additions and 32 deletions

View file

@ -1,5 +1,9 @@
# Change Log # Change Log
## 4.3.0
* Added `jaelTemplatePreload` to preload all JAEL templates into a cache
## 4.2.3 ## 4.2.3
* Turned on generated HTML minification by default * 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`. 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_framework/http.dart';
import 'package:angel3_jael/angel3_jael.dart'; import 'package:angel3_jael/angel3_jael.dart';
import 'package:file/local.dart'; import 'package:file/local.dart';
import 'package:jael3/jael3.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
main() async { main() async {

View file

@ -17,11 +17,12 @@ AngelConfigurer jael(Directory viewsDirectory,
{String fileExtension = '.jael', {String fileExtension = '.jael',
bool strictResolution = false, bool strictResolution = false,
bool cacheViews = true, bool cacheViews = true,
Map<String, Document>? cache,
Iterable<Patcher> patch = const [], Iterable<Patcher> patch = const [],
bool asDSX = false, bool asDSX = false,
bool minified = true, bool minified = true,
CodeBuffer Function()? createBuffer}) { CodeBuffer Function()? createBuffer}) {
var cache = <String, Document>{}; var _cache = cache ?? <String, Document>{};
var bufferFunc = createBuffer ?? () => CodeBuffer(); var bufferFunc = createBuffer ?? () => CodeBuffer();
@ -34,31 +35,20 @@ AngelConfigurer jael(Directory viewsDirectory,
var errors = <JaelError>[]; var errors = <JaelError>[];
Document? processed; Document? processed;
if (cacheViews && cache.containsKey(name)) { //var stopwatch = Stopwatch()..start();
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");
}
try { if (cacheViews && _cache.containsKey(name)) {
processed = await (resolve(doc, viewsDirectory, processed = _cache[name];
patch: patch, onError: errors.add)); } else {
} catch (e) { processed = await _loadViewTemplate(viewsDirectory, name,
// Ignore these errors, so that we can show syntax errors. fileExtension: fileExtension, asDSX: asDSX, patch: patch);
}
if (processed == null) {
throw ArgumentError(name + fileExtension + " does not exists");
}
if (cacheViews) { if (cacheViews) {
cache[name] = processed; _cache[name] = processed!;
} }
} }
//print('Time executed: ${stopwatch.elapsed.inMilliseconds}');
//stopwatch.stop();
var buf = bufferFunc(); var buf = bufferFunc();
var scope = SymbolTable( var scope = SymbolTable(
@ -69,7 +59,7 @@ AngelConfigurer jael(Directory viewsDirectory,
if (errors.isEmpty) { if (errors.isEmpty) {
try { try {
const Renderer().render(processed!, buf, scope, const Renderer().render(processed!, buf, scope,
strictResolution: strictResolution == true); strictResolution: strictResolution);
return buf.toString(); return buf.toString();
} on JaelError catch (e) { } on JaelError catch (e) {
errors.add(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 name: angel3_jael
version: 4.2.3 version: 4.3.0
description: Angel support for the Jael templating engine, similar to Blade or Liquid. description: Angel support for the Jael templating engine, similar to Blade or Liquid.
homepage: https://angel3-framework.web.app/ homepage: https://angel3-framework.web.app/
repository: https://github.com/dukefirehawk/angel/tree/master/packages/jael/angel_jael repository: https://github.com/dukefirehawk/angel/tree/master/packages/jael/angel_jael
@ -13,6 +13,7 @@ dependencies:
jael3_preprocessor: ^4.2.0 jael3_preprocessor: ^4.2.0
file: ^6.0.0 file: ^6.0.0
logging: ^1.0.1 logging: ^1.0.1
dev_dependencies: dev_dependencies:
angel3_test: ^4.0.0 angel3_test: ^4.0.0
html: ^0.15.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:angel3_test/angel3_test.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:html/parser.dart' as html; import 'package:html/parser.dart' as html;
import 'package:jael3/jael3.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
@ -40,13 +41,17 @@ void main() {
</extend> </extend>
'''); ''');
app.get('/github/:username', (req, res) { app.get('/github/:username', (req, res) async {
var username = req.params['username']; var username = req.params['username'];
return res.render('github', {'username': username}); return res.render('github', {'username': username});
}); });
//Preload the view template
var viewCache = <String, Document>{};
jaelTemplatePreload(viewsDirectory, viewCache);
await app.configure( await app.configure(
jael(viewsDirectory, cacheViews: true), jael(viewsDirectory, cache: viewCache),
); );
app.fallback((req, res) => throw AngelHttpException.notFound()); app.fallback((req, res) => throw AngelHttpException.notFound());
@ -73,16 +78,19 @@ void main() {
.outerHtml); .outerHtml);
}); });
test('can render multiples', () async { test('initial load concurreny', () async {
// Load the view template and wait for it to be cached // Concurrently hit the same JAEL page
var response1 = await client.get(Uri.parse('/github/thosakwe')); for (var i = 0; i < 512; i++) {
for (var i = 0; i < 100; i++) {
client.get(Uri.parse('/github/thosakwe')); 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( expect(
html.parse(response.body).outerHtml, html.parse(response.body).outerHtml,
html html