This is a follow-up code example for this post introducing Progressive Bundling: Progressive Bundling Post.
Check out the live example here: Live Example
Let's step through a project that has progressive bundling enabled to see how you might be able to use this approach in your next project.
You can follow along at home with this repo: begin-examples/node-progressive-bundling
Alternatively you can deploy your own on Begin by clicking this link: Deploy to Begin
To see the Progressive Bundling pattern in action, open up your browser's development tools to watch the network as the page loads.
Watch for a request to be made for home.js
, and you should see a 302 redirect to a bundled and fingerprinted version of that module.
Project overview
Now that we've seen it in action let's get acquainted with the project. This is a standard Architect serverless project and while you don't need to know Architect to follow along you might want to have arc.codes open in case any questions come up.
Let's start at the beginning and look at how to configure the project's infrastructure.
.arc
The project's .arc
file is how we declare this app's infrastructure as code. We declare all of the app's HTTP Functions under the @http
pragma.
@app
pb-example
@static
folder public
@http
get /
get /about
get /modules/:type/:module
get /cache
post /cache
@tables
data
scopeID *String
dataID **String
ttl TTL
folder structure
├── src/
│ └── http/
│ │ └── get-index/
│ └── views/
│ └── modules/
│ └── shared/
└── .arc
src/http
This folder is where all the HTTP function's source code lives. Architect maps these HTTP Functions to the routes declared under the @http
pragma in the app's .arc
file.
By convention,
get-index/
is mapped to the route '/' and serveshttps://yoursite/
. Similarly,get-about
is assigned to/about
and serveshttps://yoursite/about
. Apost-cache
route would accept posts from forms having the action attribute set toaction="/cache"
.
src/shared
This folder is where you put code you want to share between functions. Any code placed inside src/shared
will be available to require from @architect/shared
.
src/views
This folder is where all of your shared view code lives. These files are copied to all the GET
routes by default allowing you to share view code throughout your app. Any code placed inside src/views
will be available to require from @architect/views
.
Implementation
This example app implements the Progressive Bundling pattern with a few discrete steps.
- cache lookup: First, we check to see if the file already exists in the cache. If it does, we return it.
- bundle: Then if the requested file isn't already in the cache, we bundle it.
- fingerprint: Next, we generate a fingerprint id and append it to the file name.
- cache Then this new file is cached for subsequent requests.
- redirect: Finally, we redirect to the newly cached file.
It is possible to create a fingerprint by using a hash of the bundled file's contents. Appending a fingerprint to the file name ensures that the file name is updated when the content of the bundle changes. Adding a fingerprint to the file name avoids getting an outdated file due to a cached version in the end user's browser or network.
Now that we know the steps let's follow a request for a module through to the bundled response.
The route we are going to focus on first is get /modules/:type/:module
. This route passes a module type and module name to our HTTP Function as parameters on the request.
The folder for this route is generated by Architect the first time you start the sandbox server. The folder for this route replaces the colons ":" that defines a route to a system-friendly three zeros "000". This means that the route
get /modules/:type/:module
will generate the foldersrc/http/get-modules-000type-000module
.
src/http/get-modules-000type-000module
// Check to see if file is in cache
let file = await read({ name })
// Bundle the file if it is not found in the cache
if (!file) {
file = await bundle({ name })
}
// Redirect to the file
return redirect(`/_static/${ file }`)
The above code is where all the action is in this file. This code first looks to see if there is a cached version to send; if not, it bundles the requested file then redirects the request to the bundled version.
Let's look at how we have implemented the cache.
const data = require('@begin/data')
// check the cache manifest
let cache = await data.get({
table: 'module-cache',
key: name
})
In the previous code block, we look for the module by name in a table called module-cache
then return the file if found. In this example, we use Begin data for simplicity, but any data store would work.
Next is the code responsible for bundling the file.
src/http/get-modules-000type-000module/bundle.js
// Get the path to this module on disk
let input = path({ name })
// Pass the file path to rollup
let bundle = await rollup.rollup({ input })
// Bundle together modules
let bundled = await bundle.generate({ format: 'esm' })
Above we look up the file and then pass it to rollup to bundle. Rollup was used in this example, but you could substitute the bundler of your choice.
One last stop at the redirect code before sending the response.
src/http/get-modules-000type-000module/302.js
module.exports = function redirect(location) {
return {
statusCode: 302,
headers: { location }
}
}
Pretty straight forward, but it's useful to see how to send any type of reponse with a status code.
Wrapping up
We stepped through a project implementing this pattern and looked at all the places this pattern can be customized to fit your own needs. This example illustrates how Progressive Bundling can be implemented but is by no means the end. You can apply this pattern in your way and with other outputs. We can't wait to see what you make!
Top comments (0)