diff --git a/packages/jael/angel_jael/CHANGELOG.md b/packages/jael/angel_jael/CHANGELOG.md index 584dd6bd..b560d24d 100644 --- a/packages/jael/angel_jael/CHANGELOG.md +++ b/packages/jael/angel_jael/CHANGELOG.md @@ -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 diff --git a/packages/jael/angel_jael/README.md b/packages/jael/angel_jael/README.md index c20331cd..bd653c8c 100644 --- a/packages/jael/angel_jael/README.md +++ b/packages/jael/angel_jael/README.md @@ -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 = {}; + jaelTemplatePreload(templateDir, viewCache); + + // Inject cache into JAEL renderer + await app.configure( + jael(fileSystem.directory('views'), cache: viewCache), + ); + +``` diff --git a/packages/jael/angel_jael/example/main.dart b/packages/jael/angel_jael/example/main.dart index c55e7513..b78cf368 100644 --- a/packages/jael/angel_jael/example/main.dart +++ b/packages/jael/angel_jael/example/main.dart @@ -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 { diff --git a/packages/jael/angel_jael/lib/angel3_jael.dart b/packages/jael/angel_jael/lib/angel3_jael.dart index 64b7ab9a..5ae2df98 100644 --- a/packages/jael/angel_jael/lib/angel3_jael.dart +++ b/packages/jael/angel_jael/lib/angel3_jael.dart @@ -17,11 +17,12 @@ AngelConfigurer jael(Directory viewsDirectory, {String fileExtension = '.jael', bool strictResolution = false, bool cacheViews = true, + Map? cache, Iterable patch = const [], bool asDSX = false, bool minified = true, CodeBuffer Function()? createBuffer}) { - var cache = {}; + var _cache = cache ?? {}; var bufferFunc = createBuffer ?? () => CodeBuffer(); @@ -34,31 +35,20 @@ AngelConfigurer jael(Directory viewsDirectory, var errors = []; 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 jaelTemplatePreload( + Directory viewsDirectory, Map cache, + {String fileExtension = '.jael', + bool asDSX = false, + Iterable 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 _loadViewTemplate(Directory viewsDirectory, String name, + {String fileExtension = '.jael', + bool asDSX = false, + Iterable patch = const []}) async { + var errors = []; + 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; +} diff --git a/packages/jael/angel_jael/pubspec.yaml b/packages/jael/angel_jael/pubspec.yaml index dc01da49..35070175 100644 --- a/packages/jael/angel_jael/pubspec.yaml +++ b/packages/jael/angel_jael/pubspec.yaml @@ -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 diff --git a/packages/jael/angel_jael/test/minified_test.dart b/packages/jael/angel_jael/test/minified_test.dart index 9c87f82d..c2085453 100644 --- a/packages/jael/angel_jael/test/minified_test.dart +++ b/packages/jael/angel_jael/test/minified_test.dart @@ -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() { '''); - 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 = {}; + 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