Last active
August 25, 2021 08:21
-
-
Save GZGavinZhao/3763a6ba4d1bbadff0160d84c2f6a8ef to your computer and use it in GitHub Desktop.
AngularDart Server-Side Rendering Script with Puppeteer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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