Skip to content

Instantly share code, notes, and snippets.

@GZGavinZhao
Last active August 25, 2021 08:21
Show Gist options
  • Save GZGavinZhao/3763a6ba4d1bbadff0160d84c2f6a8ef to your computer and use it in GitHub Desktop.
Save GZGavinZhao/3763a6ba4d1bbadff0160d84c2f6a8ef to your computer and use it in GitHub Desktop.
AngularDart Server-Side Rendering Script with Puppeteer
// Put it in your bin folder, build the site, and run `dart run bin/server.dart`
// Go to http://localhost:8080 to see the result! o(^▽^)o
//
// Remember to open in an incognito or guest window, or the browser might use
// the cache instead of letting the server render it (yes FireFox that's you).
import 'dart:io';
// Remember to add these dependencies!
import 'package:mime/mime.dart';
import 'package:path/path.dart' as p;
import 'package:pedantic/pedantic.dart';
// IMPORTANT: first add puppeteer to pubspec (pub add puppeteer), or it might conflict with shelf
import 'package:puppeteer/puppeteer.dart' as puppet;
import 'package:shelf_static/shelf_static.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf/shelf.dart';
/// Replace this with the executable path for Chromium OR Chrome.
/// On Linux, run `which chrome` or `which chromium` to find it.
///
/// If you don't have Chromium or don't want to install it, comment out line 39.
/// Then the script will download a local Chromium copy (size around 300MB) to
/// folder .local-chromium
String chromiumExecutablePath = '/opt/google/chrome/chrome';
/// Use port `0.0.0.0` for deploying (most of the time).
String address = 'localhost';
/// When deploying use port 80 for HTTP, 443 for HTTPS (most of the time)
int port = 8080;
/// Acts like an address for the location of the shared browser instance.
String browserWSEndpoint;
void main() async {
if (browserWSEndpoint == null) {
var browser = await puppet.puppeteer.launch(
// Comment out line below if no Chromium
executablePath: chromiumExecutablePath,
// noSandboxFlag: true, // Uncomment this line if you will run as root
args: ['--disable-gpu'],
);
browserWSEndpoint = browser.wsEndpoint;
}
var handler =
const Pipeline().addMiddleware(logRequests()).addHandler(_handleRequest);
var static = createStaticHandler('build', defaultDocument: 'index.html');
// You can comment out this line for a small performance boost if you are not using router and not running as root.
// For details, see comment for the ssr() function.
unawaited(io.serve(static, 'localhost', 9090));
unawaited(io.serve(handler, address, port));
print('Server has started... Go to http://$address:$port to see it.');
}
Future<Response> _handleRequest(Request request) async {
bool needsRender = false;
var targetFile = File(p.join('build', p.basename(request.requestedUri.path)));
// print(targetFile.path);
if (p.extension(request.requestedUri.path) == '') {
// It's the actual site request! Return the html
targetFile = File(p.join('build', 'index.html'));
needsRender = true;
}
// This particularly deals with the annoying error you get when the browser
// asks for `favicon.ico` when viewing the page source. It's safe to remove
// the below `if` statement if it interferes with your own code logic.
if (!(await targetFile.exists())) {
return Response.notFound('File $targetFile doesn\'t exist!');
}
return Response.ok(
needsRender ? await ssr(targetFile.path) : targetFile.openRead(),
headers: {
HttpHeaders.contentTypeHeader: MimeTypeResolver().lookup(targetFile.path),
},
);
}
/// Server-Side Rendering function using Chrome Puppeteer
///
/// You can uncomment the two lines and comment the line below it if you are not
/// using router. This theoretically makes the rendering a bit faster. However,
/// do not run as root under this circumstance, or it might break.
Future<String> ssr(String url) async {
final timer = Stopwatch()..start();
// final target = File(url).absolute.path;
print('\nConnecting to existing Chrome instance...');
var browser =
await puppet.puppeteer.connect(browserWsEndpoint: browserWSEndpoint);
var page = await browser.newPage();
// await page.goto(Uri.file(target, windows: Platform.isWindows).toString(), wait: puppet.Until.load);
await page.goto('http://localhost:9090', wait: puppet.Until.load);
final html = await page.content;
await page.close();
print('Render time: ${timer.elapsed}');
timer.stop();
return html;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment