From 7c60e97dc7e089693dc0d4a4e8d8af08bd89328b Mon Sep 17 00:00:00 2001 From: Kilian Schulte Date: Mon, 30 Dec 2024 19:41:27 +0100 Subject: [PATCH] add standalone option --- packages/jaspr/CHANGELOG.md | 1 + .../adapters/client_script_adapter.dart | 6 ++-- .../adapters/global_styles_adapter.dart | 6 ++-- .../server/adapters/head_scope_adapter.dart | 11 ++++++-- .../lib/src/server/render_functions.dart | 11 ++++---- packages/jaspr/lib/src/server/run_app.dart | 8 ++++-- .../jaspr/lib/src/server/server_binding.dart | 4 +-- .../jaspr/lib/src/server/server_handler.dart | 2 +- .../{formatting => }/formatting_test.dart | 0 packages/jaspr/test/server/render_test.dart | 28 +++++++++++++++++++ 10 files changed, 59 insertions(+), 18 deletions(-) rename packages/jaspr/test/server/{formatting => }/formatting_test.dart (100%) create mode 100644 packages/jaspr/test/server/render_test.dart diff --git a/packages/jaspr/CHANGELOG.md b/packages/jaspr/CHANGELOG.md index 11b1b141..fe3390e2 100644 --- a/packages/jaspr/CHANGELOG.md +++ b/packages/jaspr/CHANGELOG.md @@ -2,6 +2,7 @@ - Added `table` and related html methods. - Added `value` parameter to `select()` method. +- Add 'standalone' option to `renderComponent` method. ## 0.16.2 diff --git a/packages/jaspr/lib/src/server/adapters/client_script_adapter.dart b/packages/jaspr/lib/src/server/adapters/client_script_adapter.dart index 2ecdd476..79883405 100644 --- a/packages/jaspr/lib/src/server/adapters/client_script_adapter.dart +++ b/packages/jaspr/lib/src/server/adapters/client_script_adapter.dart @@ -10,9 +10,9 @@ class ClientScriptAdapter extends HeadScopeAdapter { final List clientTargets; @override - void applyHead(MarkupRenderObject head) { + bool applyHead(MarkupRenderObject head) { if (clientTargets.isEmpty) { - return; + return false; } String source; @@ -27,5 +27,7 @@ class ClientScriptAdapter extends HeadScopeAdapter { head.createChildRenderObject() ..updateElement('script', null, null, null, {'src': '$source.dart.js', 'defer': ''}, null), ); + + return true; } } diff --git a/packages/jaspr/lib/src/server/adapters/global_styles_adapter.dart b/packages/jaspr/lib/src/server/adapters/global_styles_adapter.dart index 16c707dd..1b0d0689 100644 --- a/packages/jaspr/lib/src/server/adapters/global_styles_adapter.dart +++ b/packages/jaspr/lib/src/server/adapters/global_styles_adapter.dart @@ -6,10 +6,10 @@ class GlobalStylesAdapter extends HeadScopeAdapter { final ServerAppBinding binding; @override - void applyHead(MarkupRenderObject head) { + bool applyHead(MarkupRenderObject head) { var styles = binding.options.styles?.call() ?? []; if (styles.isEmpty) { - return; + return false; } head.children.insertBefore( @@ -17,5 +17,7 @@ class GlobalStylesAdapter extends HeadScopeAdapter { ..updateElement('style', null, null, null, null, null) ..children.insertBefore(head.createChildRenderObject()..updateText(styles.render(), true)), ); + + return true; } } diff --git a/packages/jaspr/lib/src/server/adapters/head_scope_adapter.dart b/packages/jaspr/lib/src/server/adapters/head_scope_adapter.dart index 9193c614..7819ba0b 100644 --- a/packages/jaspr/lib/src/server/adapters/head_scope_adapter.dart +++ b/packages/jaspr/lib/src/server/adapters/head_scope_adapter.dart @@ -10,12 +10,17 @@ abstract class HeadScopeAdapter extends RenderAdapter { var html = root.children.findWhere((c) => c.tag == 'html')?.node ?? root; var head = html.children.findWhere((c) => c.tag == 'head')?.node; + bool needsInsert = false; if (head == null) { - html.children.insertAfter(head = html.createChildRenderObject()..tag = 'head'); + head = html.createChildRenderObject()..tag = 'head'; + needsInsert = true; } - applyHead(head); + var didApply = applyHead(head); + if (didApply && needsInsert) { + html.children.insertAfter(head); + } } - void applyHead(MarkupRenderObject head); + bool applyHead(MarkupRenderObject head); } diff --git a/packages/jaspr/lib/src/server/render_functions.dart b/packages/jaspr/lib/src/server/render_functions.dart index 1c33896d..6b32545e 100644 --- a/packages/jaspr/lib/src/server/render_functions.dart +++ b/packages/jaspr/lib/src/server/render_functions.dart @@ -10,13 +10,13 @@ typedef FileLoader = Future Function(String); /// Performs the rendering process and provides the created [AppBinding] to [setup]. /// /// If [Jaspr.useIsolates] is true, this spawns an isolate for each render. -Future render(SetupFunction setup, Uri requestUri, FileLoader loadFile) async { +Future render(SetupFunction setup, Uri requestUri, FileLoader loadFile, bool standalone) async { if (!Jaspr.useIsolates) { var binding = ServerAppBinding() ..setCurrentUri(requestUri) ..setFileHandler(loadFile); setup(binding); - return binding.render(); + return binding.render(standalone: standalone); } var resultCompleter = Completer.sync(); @@ -38,7 +38,7 @@ Future render(SetupFunction setup, Uri requestUri, FileLoader loadFile) }); try { - var message = _RenderMessage(setup, requestUri, port.sendPort); + var message = _RenderMessage(setup, requestUri, standalone, port.sendPort); await Isolate.spawn(_render, message, onError: errorPort.sendPort); return await resultCompleter.future; @@ -62,16 +62,17 @@ void _render(_RenderMessage message) async { }); message.setup(binding); - var result = await binding.render(); + var result = await binding.render(standalone: message.standalone); message.sendPort.send(result); } class _RenderMessage { final SetupFunction setup; final Uri requestUri; + final bool standalone; final SendPort sendPort; - _RenderMessage(this.setup, this.requestUri, this.sendPort); + _RenderMessage(this.setup, this.requestUri, this.standalone, this.sendPort); } class _LoadFileRequest { diff --git a/packages/jaspr/lib/src/server/run_app.dart b/packages/jaspr/lib/src/server/run_app.dart index d69c03a6..106d8479 100644 --- a/packages/jaspr/lib/src/server/run_app.dart +++ b/packages/jaspr/lib/src/server/run_app.dart @@ -29,14 +29,16 @@ Handler serveApp(AppHandler handler) { typedef RenderFunction = FutureOr Function(Component); typedef AppHandler = FutureOr Function(Request, RenderFunction render); -/// Directly renders the provided component into a html string -Future renderComponent(Component app) async { +/// Directly renders the provided component into a html string. +/// +/// When [standalone] is false (default), the html output will have a full document structure (html, head, body). +Future renderComponent(Component app, {bool standalone = false}) async { _checkInitialized('renderComponent'); var fileHandler = staticFileHandler(); return render(_createSetup(app), Uri.parse('https://0.0.0.0/'), (name) async { var response = await fileHandler(Request('get', Uri.parse('https://0.0.0.0/$name'))); return response.statusCode == 200 ? response.readAsString() : null; - }); + }, standalone); } void _checkInitialized(String method) { diff --git a/packages/jaspr/lib/src/server/server_binding.dart b/packages/jaspr/lib/src/server/server_binding.dart index 5ac26e75..00e2d76a 100644 --- a/packages/jaspr/lib/src/server/server_binding.dart +++ b/packages/jaspr/lib/src/server/server_binding.dart @@ -25,7 +25,7 @@ class ServerAppBinding extends AppBinding with ComponentsBinding { super.attachRootComponent(ClientComponentRegistry(child: app)); } - Future render() async { + Future render({bool standalone = false}) async { if (rootElement == null) return ''; if (rootElement!.owner.isFirstBuild) { @@ -38,7 +38,7 @@ class ServerAppBinding extends AppBinding with ComponentsBinding { var adapters = [ ..._adapters.reversed, GlobalStylesAdapter(this), - DocumentAdapter(), + if (!standalone) DocumentAdapter(), ]; for (var adapter in adapters.reversed) { diff --git a/packages/jaspr/lib/src/server/server_handler.dart b/packages/jaspr/lib/src/server/server_handler.dart index 60778628..22aa9aed 100644 --- a/packages/jaspr/lib/src/server/server_handler.dart +++ b/packages/jaspr/lib/src/server/server_handler.dart @@ -86,7 +86,7 @@ Handler createHandler(SetupHandler handle, {http.Client? client, Handler? fileHa } return Response.ok( - await render(setup, requestUri, fileLoader), + await render(setup, requestUri, fileLoader, false), headers: {'Content-Type': 'text/html'}, ); }); diff --git a/packages/jaspr/test/server/formatting/formatting_test.dart b/packages/jaspr/test/server/formatting_test.dart similarity index 100% rename from packages/jaspr/test/server/formatting/formatting_test.dart rename to packages/jaspr/test/server/formatting_test.dart diff --git a/packages/jaspr/test/server/render_test.dart b/packages/jaspr/test/server/render_test.dart new file mode 100644 index 00000000..e646dc3f --- /dev/null +++ b/packages/jaspr/test/server/render_test.dart @@ -0,0 +1,28 @@ +@TestOn('vm') + +import 'package:jaspr/server.dart'; +import 'package:jaspr_test/server_test.dart'; + +void main() { + group('render test', () { + setUpAll(() { + Jaspr.initializeApp(useIsolates: false); + }); + + test('renders component with document', () async { + var result = await renderComponent(div(id: 'test', [])); + + expect( + result, + equals('\n' + '
\n' + '')); + }); + + test('renders standalone component', () async { + var result = await renderComponent(div(id: 'test', []), standalone: true); + + expect(result, equals('
\n')); + }); + }); +}