name: "@fab/static" route: "/packages/fab-static" menu: Packages
💎 @fab/static
Builds a FAB from a directory of static HTML and assets, with an optional server-side component.
Getting Started
Install @fab/static
as a development dependency:
yarn add --dev @fab/static
npm install --dev @fab/static
Add a couple of scripts to your package.json
:
"scripts": {
"build": "[your existing build step]",
+ "build:fab": "npm run build && npm run fab:compile",
+ "fab:compile": "fab-static [dirname]"
},
}
Replace
dirname
with wherever your built files live (build
for Create React App & friends,public
for Gatsby,dist
for some others)
We've added two scripts here, fab:compile
which runs the fab-static
builder, and build:fab
that builds the project first. Most of the time, and especially you're using a FAB-enabled platform like linc.sh, you'll mostly run build:fab
, but having a separate fab:compile
step can be handy as you set things up.
You can test it out by running:
npm run build:fab
Once this is complete, you should have a fab.zip
file (and a .fab
directory with a bunch of build files). Those don't need to be checked in to your repository, so you can add them to your .gitignore
file with this one-liner:
echo "\n.fab\nfab.zip" >> .gitignore
If you want to spin up your fab.zip
file locally, you can use @fab/serve. You can either install it globally:
yarn global add @fab/serve
npm install --global @fab/serve
fab-serve fab.zip
Or use the awesome npx (which is bundled with NodeJS) to run a command-line NPM package without needing to install it:
npx @fab/serve fab.zip
You should see your app running on http://localhost:3000
!
Note: this process will add one file fab.zip
and one directory .fab
into your project.
Usage
@fab/static
takes the following options:
USAGE
$ fab-static [DIRECTORY]
OPTIONS
-h, --help show CLI help
-o, --output=output Output FAB file (default fab.zip)
-p, --prod-settings=prod-settings Path to production settings json
-s, --server=server Path to server entry file or directory
-w, --working-dir=working-dir Intermediate directory for working (default .fab)
--intermediate-only
EXAMPLE
$ fab-static ~/src/my-project/build
Efficiently handling assets
The fab.zip
file only allows two components—the first is a server.js
, which is auto-generated by @fab/static
, and the second is an _assets
directory. If your assets aren't in an _assets
subdirectory (e.g. a public
or static
directory instead), @fab/static
will automatically move them and generate server-side code to wire things up at runtime. This process works fine, but it's inefficient.
To read more about the rewriting process, visit the @fab/compile documentation, or for a more general overview of why _assets
is a core part of the FAB spec, visit https://fab.dev/kb/no-assets-dir
Environment Variables
The FAB format has first-class support for environment variables which you can read about here: https://fab.dev/kb/environment-variables.
To specify production environment variables to be bundled into the FAB itself, add a production-settings.json
file in the same directory as your package.json
file and it will be automatically included into the build. You can also specify a path with -p
or --prod-settings
if you'd prefer to keep your settings file elsewhere/name it differently.
A production settings file might look like:
{
"API_URL": "https://api.example.com",
"CLIENT_ID": "1cd5e6a4871f98616a38abb2eada56868ca5aad2"
}
These values are for the production environment (read why). To supply non-production variables that will be injected in place of these settings, consult the documentation of your hosting platform e.g. Linc
Accessing Environment Variables at Runtime
@fab/static dynamically injects a <script>
tag into any HTML response as it's being streamed to the client:
<!DOCTYPE html>
<html lang="en">
<head>
<!-- FAB SETTINGS SCRIPT TAG INJECTED HERE -->
<meta charset="UTF-8" />
<title>Document</title>
<!-- ... -->
</head>
<body>
<!-- ... -->
</body>
</html>
That <script>
tag has the following format:
<script type="text/javascript">
window.FAB_SETTINGS = {
"API_URL": "https://staging.api.example.com"
}
</script>
Whatever the settings
object passed to render
(which will be a combination of production settings as well as any environment-specific overrides), @fab/static will serialise them to JSON and inject them as window.FAB_SETTINGS
. This code will execute before any other, meaning you can always rely on FAB_SETTINGS
to be available by the time your app is booted.
fetch(`${window.FAB_SETTINGS.API_URL}/endpoint`).then(
// ...
)
To use the same environment variables during development, it's recommended to add a layer of abstraction between FAB_SETTINGS
(available once the FAB is built) and process.env
(available during development). For example
// src/config.js
const lookupEnvVar = name => {
// Use FAB_SETTINGS if defined
if (typeof FAB_SETTINGS === 'object') {
return FAB_SETTINGS[name]
// Otherwise use process.env
} else {
// Note: some build systems (like Create React App) only expose
// process.env vars that start with a prefix (like REACT_APP_)
return process.env[`REACT_APP_${name}`]
}
}
export default {
API_URL: lookupEnvVar('API_URL'),
API_KEY: lookupEnvVar('API_KEY'),
// ...
}
You can use the config
throughout your app like so:
import config from '../config'
fetch(`${config.API_URL}/endpoint`).then(
// ...
)
Adding a server
Despite the name, @fab/static
is capable of running virtually any server-side logic you'd like. From use-cases like rewriting or forwarding URLs, proxying APIs, or simply providing a health check, you can supercharge your FAB by using the -s
or --server
flag to fab-static
.
For simple use-cases, you can use a single file:
fab-static --server path/to/server.js
For more complex setups, you can specify a whole directory which will be bundled into the FAB:
fab-static --server path/to/server
In this example, the path/to/server/index.js
will be used as the entry point, but the whole directory will be copied into the FAB.
Server API
Your server.js
or server/index.js
file can export one of three endpoints:
export async function modifyRequest(settings, request) {
// default behaviour is nothing, needs no return value
}
export async function route(settings, path, request) {
// must return something, by default returns the path unchanged
return path
}
export async function modifyResponse(settings, response) {
// default behaviour is nothing, needs no return value
}