I use this with ClojureScript and Logtalk with SWI-Prolog on the backend, but there’s no reason it shouldn’t work with other SPA’s (I’ve also used VueJS) and without Logtalk. If Web-Development with SWI-Prolog is new to you, you might want to run through Annie’s fantastic tutorial first.
Step 1: Use SWI-Prolog to Serve the App Page
SPA applications work with an
index.html page you’ll find somewhere in your
SPA directory. In that
index.html there will be a
<div/> or some such tag
where the application will appear, as well as some scripts that make the magic
happen. You can either copy this file across to your SWI-Prolog directory and
have SWI-Prolog serve it, or recreate it with termerized HTML.
Usually I opt for termerized HTML so I can use it for consistency across webpages, such as having the same navbar. I also use termerized HTML so I can get dynamic behaviour, such as only rendering a “Logout” button if a user is logged in. So to achieve Step 1 here for ClojureScript, I’d have something like this:
js/compiled/myapp.js. In turn, this triggers requests for a
are visible to my SWI-Prolog application. Also, none of these files are
used in production: by then they’re all compiled down to one “little” file.
So what I want SWI-Prolog to do when it’s asked for these files is to ask
whatever is serving my SPA for them. This means I need whatever process is
used to typically serve my SPA also running during development, for
lein figwheel. That serves on
you’ll need to adapt the code below for your SPA.
I get SWI-Prolog to serves these files by combining the
prefix we use for
serving static files with SWI-Prolog’s HTTP Client libraries. In my app the
requests to the scripts are relative to the URL being served, so it’s actually
going to ask for
/myapp/js/compiled/myapp.js, which is why the handler here
root(myapp). If yours is an absolute URL, adapt accordingly.
:- use_module(library(http_client), [ http_get/3 ]). :- use_module(library(http_open), [ http_close_keep_alive/1 ]). :- use_module(library(http_header), [ http_reply_header/3 ]). :- handler(root(myapp), spa_proxy, [id(spa_proxy), prefix, priority(-1)]). handle(Request) :- proxy_url(Request, Url), proxy(Url). proxy_url(Request, Url) :- member(path_info(RelPath), Request) ; RelPath = '/'), !, % comment these writes and nl out when you've seen it working! write(user_output, 'Forwarding: '), write(user_output, RelPath), nl(user_output), % adapt the base url to your spa atom_concat('http://localhost:3449', RelPath, URL). proxy(Url) :- http_get(Url, Data, [ headers(Headers), to(string), connection('Keep-alive')]), http_close_keep_alive(URL), member(content_type(ContentType), Headers), write('Content-type: '), write(ContentType), nl, nl, write(Data).
Step 3: Enjoy!
That should be about all you need. Spin up both processes, navigate to your SPA
app served by SWI-Prolog, and watch the magic happen. When it comes to
production deployment just change the
myapp handler to return HTML that loads
spa_proxy handler. I do this with Logtalk, so for me this is just
commenting out the a file in the
loader.lgt, but you can do the same with
SWI-Prolog if you also use some kind of loader file.
This was a bit of a pain to figure out the first time, but has saved me so much bother and made developing frontend applications on a Prolog backend quite practical. I hope it helps you out too!