Part of the Solid Foundations Learning Series
This is an in-depth learning series focused on a specific application: a JavaScript-based suite of single-page applications optimized for use in a microservice environment. We focus on telling the story of “why” and “how” it was built.
—
In our prior segments of this series, we addressed being able to add in pages to the application and have them automatically registered with the navigation. We also discussed having Markdown content associated with different pages of the application.
In this segment, we will talk about the philosophy of using smaller single-page applications in place of large ones. We’ll discuss how to add in a single-page application to this application. And we will dive into a couple of the example single-page applications and talk about how they were built specifically.
Theory of an SPA
Let us first think about single-page applications and how they’re typically built. In general, a modern SPA uses a fairly large tech stack to get everything to run correctly. We have build systems where we compile, combine and merge things together. We have hundreds of dependencies for our development environments and the need to know a dozen or more different toolings and whatnot. As the application grows, so does the overhead of the development.
Then you start to have issues with the potential of memory leaks during usage. Or having too much happening in the browser, which slows down development while developers struggle to figure out what to optimize where.
If the single-page application is fairly large, you may have a dozen or more developers working on it. Each team might be creating new features that all depend upon that one common denominator: the single page, which is rendered to the browser initially. Development can slow, features can stall out, and the business struggles to keep up with demand.
So what if instead of one single application, you created many small to medium applications and group them together into one workspace suite?
That’s why you have this web application. Each view can literally be the index of a compiled single-page application. Build your SPA in React, Angular, AngularJS, Vue, whatever, outside of this framework. Have the application be specific for a task. Have developers learn that one application and be able to maintain it well. Have an API server specific for that one task created, optimized, and maintained outside of this application framework. When you’re ready to deploy a section of the suite, take the index page, toss it into the pages
folder, and watch it automagically register itself in the navigation… and you now have updated your workspace suite. This makes the idea of breaking things into smaller, more manageable sections easier and closer to the idea of microservices. Do one thing, and do it well.
So how do we do this?
As we’ve discussed earlier, each Handlebars view is essentially just a few partial sections combined to make a single page. For example, here is a simple about
page:
{{> header }} {{> navigation }} {{!-- triple curly braces for literal content --}} {{{data.contents}}} {{> footer }}
The header
, the navigation
, and the footer
create the actual base HTML of the page. The contents
of the page come from our Markdown service, and isn’t always necessary when the page renders.
With that in mind, we can easily take a single-page application index
page, strip out those parts that are not necessary, wrap it in our partial components, and create our own index page for a SPA.
Here is an example of what an older AngularJS application looks like when it has been cleaned up of unnecessary elements and wrapped in our own Handlebars components:
{{> header }} {{> navigation }} <h2><i class="fa fa-shopping-cart"></i> Products</h2> <hr /> <div ng-app="shopApp"> <ul class="nav nav-pills mb-3"> <li ng-class="nav-item"><a class="nav-link" ui-sref="home" ui-sref-active="active"><i class="fa fa-shopping-cart"></i>Shop Home</a></li> <li ng-class="nav-item"><a class="nav-link" ui-sref="about" ui-sref-active="active">About</a></li> </ul> <hr /> <div ui-view></div> </div> <script src="../vendor/angular/angular.js"></script> <script src="../vendor/angular/angular-ui-router.min.js"></script> <script src="../app_shop/app.js"></script> <script src="../app_shop/dataAccess/ShopFactory.js"></script> <script src="../app_shop/shophome/homeController.js"></script> <script src="../app_shop/services/checkoutService.js"></script> <script src="../app_shop/checkout/checkoutControler.js"></script> {{> footer }}
Notice that the header
, the navigation
, and the footer
partials are still there. But they’re now wrapping the core sections of a single-page application index
page instead of the contents
component.
The Mark Up
Let us dive in here and I will explain the mark up to you. As mentioned, we have our header
and navigation
components from the server at the top of the markup. We need these because they create the HTML head and body for the page. The application’s global navigation is needed so that this page can be navigated to during runtime.
Now we can talk about the HTML that is shown.
- The first thing you see is an
H2
element which just contains the title of the SPA, nothing special here. - Next you can drop down to the
div
which now has a very specific AngularJS attribute,ng-app
. Yes, this is where we are declaring our AngularJS application at. - A few lines further down, you can now see some navigation markup. This is not associated with our global navigation, this is localized navigation just for the single-page application. Notice that we’re using
ui-router
attributes such asui-sref
andui-sref-active
on the hyperlinks. So yes, we can have deeper navigation on our SPA’s and, the navigation will actually show in our browser’s address bar. - Under the SPA’s navigation, we have a
div
containing anotherui-router
-specific attribute,ui-view
. So, just like any other single-page application, we have the ability to swap view dynamically just as we would expect from a standalone application.
Now you will notice that on this particular SPA index page, we’re adding in script tags at the bottom of the page. Again, this was an older AngularJS application, and when it was built no build system was used to combine all of the JavaScript into one file. But I think this does show a good reference in that that page is simply referring to files found in our public
folder.
So where is the SPA at?
What you ask? Those files reference something in the public
folder? That is a valid question and let’s discuss this in depth.
Typically, when a single-page application is created, built for production, and deployed, there’s a folder that contains all of the necessary files and assets needed for the application. Everything but the index file would be in this folder. When the SPA is accessed, the index folder accesses these assets and renders the necessary views, pages, etc. Now there are many times when a single-page application has been integrated with a server-side application framework such as Microsoft’s .Net or Java’s Spring Boot framework. All of those which will render a server-created HTML page which is the starting point of the single-page application.
All of that information still applies to this implementation. The only two differences are, one, that we’re using NodeJS and Express to render the starting point of the single-page application. And two, that we’ve placed the scripts and files for the the SPA in our public
folder (that was created by default by Express), which is used specifically for assessing assets by the browser.
Here is the layout of the public
folder for our application.
├── public │ ├── app_address │ │ ├── about │ │ ├── addressbook │ │ ├── app.js │ │ └── dataAccess │ ├── app_shop │ │ ├── about │ │ ├── app.js │ │ ├── checkout │ │ ├── dataAccess │ │ ├── services │ │ └── shophome │ ├── app_vue_router │ │ ├── css │ │ └── js
As you can see, under the top level public
folder, we have a series of folder all prefixed by the word app_
. By convention I have named all of the single-page applications in this fashion to make it easy to differentiate between one another. Inside the public
folder, we have three different single-page applications, all which have a corresponding Handlebars view
page in our pages/views
directory.
Okay, but how does it really work?
This is the flow of how one of our single-page applications is brought to life within our application.
- Drop the single-page applications assets into a folder named
app_
whatever. - Remove all unnecessary HTML elements from the SPA’s index page and wrap the remaining HTML elements with the Handlebars components (such as we described above).
- Update the references to the SPA’s scripts to be
../app_
and the rest of the path. - Move the file into the
pages/views
folder - At this time, the server side services will find the file and create the navigation for the SPA’s Handlebars page. It will now be registered within the global navigation of the application.
- Clicking on the navigation will render that initial page, and you will be within your single-page application surrounded by the shell of our Express application.
Well, okay now…
Yes, it’s a few steps. They’re not complicated, but they are manual at the moment. In reality, this is just the wire-up phase of integrating one application into another application. The goal going forward is to make this easier and less hands-on for the developer. But there are advantages of having your single-page applications served this way.
- Single responsibility SPA’s will keep your entire application from failing
- Easier to turn on and off sections by adding or removing single-page application index page
- Faster development of features when a single SPA only handles one features at a time
- Security can be implemented on not only at the SPA level, but also at the global navigation level. Permissions can granted at all levels of access to the SPA.
- The possibility of memory leaks from long-running SPAs decreases due to the fact that on a navigation change from one application feature to another, the pages are dropped and a fresh SPA is loaded each time.
This is a different approach to modern development. In fact, it may even be harking back to a different time in development. But in the end, our goals were met by having a series of smaller single-page applications successfully integrated within a larger server-rendered application. I feel that the advantages far outweigh the steps necessary to add our SPA for the first time.
In our next segment, we will dive deeper in to using a VueJS application within our shell application. Stay tuned!