I am trying to figure out how to use fastboot in our delivery environment. On our corporate servers, deployment consists of populating an IIS site with a packaged tree of files - think of it as a zip. The zip has a single build of all source, suitable for deployment in production if it passes all the stages of testing. The only thing that can differ among the deployment targets is a per-target tree of static configuration files, which must be delivered to the build with the checked-in code. After deployment, the website’s entire directory tree is effectively read-only.
For an ASP.NET MVC application using Ember-CLI, we do the ember build to the www subtree of the application. When you visit the application, the MVC controller reads the www/index.html into memory, tweaks in a containing a JSON of all of the settings supplied in the server configuration that the client will need (similar to what Ember CLI does with the environment.js), and streams the result to the HTTP response. A service in the app reads the meta from the document and serves its pieces to the rest of the application.
Enter ember-cli-fastboot. I will direct fastboot to use the www subtree of the web app as the “dist” area, of course. It seems perfectly natural for my MVC controller to grab the response from the fastboot URL rather than reading www/index.html as the basis for its HTTP response. So far so good.
However, I’m running my full app in fastboot, and it needs those server settings from my siteconfig service to populate links, etc. It can’t use document.querySelector() to pull the meta, because there is no document object from which to retrieve the data, and there is no meta there yet anyway. So it will need my siteconfig service’s init() to invoke the fastboot service to see if fastboot is running and get them some other way.
How? environment.js is buried in the minified js and is therefore unavailable to me the minute the build is completed.
-
Ideally, I’d like to supply the data dynamically from the web app whenever the web app starts up.
-
If I had to, I could supply a set of config file at build time, shadows of the data in the web.config files for each target. It’s more error-prone. The separate web.config files are already one of the hardest things for us to manage. They’re the only thing that can really mess up for the first time on a production migration.
What are my options here?
I’ve found something that should work.
At the end of build, I have the index.html for the client app. I can run a script that injects a block into a copy of the base script, one per environment, something like what fastboot does for the shoebox. When the web app is deployed, the env-specific index.html will overwrite the index.hmtl in www, just as we do for web.config files.
When fastboot loads, it will pick up the page with this script block and the data will be available to the siteconfig service in fastboot. Likewise, this block will be retained in the page generated by fastboot, so the siteconfig service can pick it up in the same way in the browser. No “if (fastboot)” code needed.
The cost of the approach is the script to generate the data at the end of build, and the exposure of a single global function or object, depending on which I put in the script, at runtime. Relatively cheap but not perfect. The problem with a global object is that anything injected into the window could overwrite it, directing things anywhere, but at the point I had to worry about that, I’d have a lot bigger things to worry about.
Thanks for the pointer! This will certainly help with some of our apps, wizards that aren’t taking query parameters and so start at the same place for all users. Others will display different results based on combinations of as many as a couple dozen query parameters. For those, fastboot is really the only viable answer for generating the pages.
Our class of applications - web-based engineering design tools - is relatively insulated from cross-session history and variable external state. Our models are based on doing a lot of complicated math in the JavaScript engine in response to user input rather than pulling user data from a database. If we can cache the pages fastboot generates and reuse them when we receive the same URLs, we should be able to get the fastest of all possible worlds.
Capping off the discussion, I’ve found a straightforward solution that should work for us. Sometimes it doesn’t pay to overthink these things. My service loads the JSON from a URL to provide configuration data to the application. The URL can be a file in /public or it can be an API server.
- Case #1: FastBoot - use fetch to load the URL, and stuff the result in the shoebox.
- Case #2: Browser after FastBoot - read the result from the shoebox - no fetch required.
- Case #3: Browser without FastBoot - what, no shoebox? Use fetch to load the URL.
if we find we are organizationally barred from deploying nodejs and fastboot on our servers, we have an escape hatch: Write a node post-build step to make alternate index.html files that encode the contents of the config files for each environment into a structure indistinguishable from the shoebox. Probably two days of development. No application code change is required and no fetch is involved, but it delivers static configuration only.
This seems like a broadly applicable solution for those with organizationally expensive file-copy-only deployments that don’t permit rebuilds per environment. I hope someone else finds this helpful.
Thank you for taking the time to write this up and share your solution. I’m sure plenty of others will have this problem as well.
I can sympathize with the pain of integrating into non-Node-native stacks. At LinkedIn, we use the JVM heavily in production, and it’s taken Cirque du Soleil-levels of contortions to get everything integrated.
If you think there are missing abstractions in the FastBoot packages that would make this easier, please let me know. I’m hoping that the fastboot
package itself is sufficiently low-level that it’s easy to adapt to many different environments, but the more we can make stuff work out-of-the-box, the better.
1 Like