Add support for attaching environment specific data to responses.
This plugin assembles data from environment-specific file(s) which is then usable by server code from the response object and can be accessed in browser code via GasketData or Redux.
gasket create <app-name> --plugins @gasket/plugin-response-data
npm i @gasket/plugin-response-data
Modify plugins
section of your gasket.config.js
:
module.exports = {
plugins: {
add: [
+ '@gasket/plugin-response-data'
]
}
}
There are two file structure methods available for defining config of different environments in apps.
- Multiple files with Environment per file
- Single file with Environments inline
By default, this plugin imports files from your /gasket-data
directory to
assemble your application's response data. You can change this directory with
the gasketData.dir
option in your gasket.config.js
:
module.exports = {
gasketData: {
dir: './src/gasket-data'
}
};
Gasket first decides which environment you're running in. By default, this
comes from the NODE_ENV
environment variable or the --env
command-line
argument which can be set from the GASKET_ENV
environment variable.
Environments can be subdivided (say, for multiple data centers) through dotted
identifiers. Example: production.v1
.
Once this environment identifier is determined, files are imported based on the
identifier and deep merged together. Files must be CommonJS .js
modules or
.json
files with the name of your environment. You may optionally have a
base.js[on]
file shared across all environments. Given the example environment
production.v1
, the following files may be merged together:
production.v1.js
production.js
base.js
If you run your application via gasket local
or explicitly set your
environment to local
, there's additional merging behavior:
local.overrides.js
local.js
development.js (or dev.js)
base.js
The optional local.overrides.js
is something that can be on individual
workstations and ignored in your .gitignore
. Depending on if you're naming
your development environment dev
or development
, that will automatically be
merged with your local
configuration.
Additionally, you can optionally add an gasket-data.config.js
file to the
root of your app's directory, alongside the gasket.config.js
. This file
allows you to set up inline environment overrides, as opposed to in separate
files, following the same rules as the gasket.config.js
.
If you happen to set up both ./gasket-data
and gasket-data.config.js
,
the configurations will be merged with ./gasket-data
taking priority.
The .json
extension is also supported for this file.
To support easy local development, you can use an gasket-data.config.local.js
which will merge in your own local configuration for development. This file
should not be committed, and specified in .gitignore
.
The plugin attaches the configuration from your config files to a gasketData
property of the Response object.
Custom Express middleware (provided you ensure that your middleware runs after
the config plugin). For example, in /lifecycles/middleware.js
:
module.exports = {
timing: {
after: ['@gasket/gasket-data']
},
handler(gasket) {
return (req, res, next) => {
if (res.gasketData.featureFlags.betaSite) {
res.redirect(res.gasketData.betaSiteURL);
} else {
next();
}
}
}
}
If you are developing a front end with Next.js, config is accessible to
server-side rendering via getInitialProps
:
import * as React from 'react';
PageComponent.getInitialProps = function({ isServer, res }) {
if (isServer) {
return {
flags: res.gasketData.featureFlags
};
}
// ...
}
If you need access to config values in client-side code, this can be done
by defining a public
property in your gasket-data.config.js
.
module.exports = {
public: {
test1: 'config value 1 here',
test2: 'config value 2 here'
}
};
TODO - how to add to html
The plugin will return these public
properties to your browser, to be
accessed by @gasket/data
. They are available like so:
import gasketData from '@gasket/data';
console.log(gasketData.test1); // config value 1 here
If you need access to config values in client-side code, this can be done
through your redux store. The config plugin looks for a public
property of
your configuration in gasket-data.config.js
and places it under a gasketData
property in your initial public state.
module.exports = {
environments: {
dev: {
public: {
url: 'https://your-dev-service-endpoint.com'
}
},
test: {
public: {
url: 'https://your-test-service-endpoint.com'
}
}
}
};
In this example, url can be selected from state.gasketData.url
with Redux
in the browser.
The gasketData
event is fired once the config file contents are normalized.
Hooks are passed the gasket API and the current configuration and should return
a new configuration object with injected modifications. This event will occur
once during startup, and hooks may be asynchronous. Sample:
const fetchRemoteConfig = require('./remote-config');
module.exports = {
hooks: {
async gasketData(gasket, gasketData) {
const remoteCfg = await fetchRemoteConfig();
return {
...gasketData,
...remoteCfg
};
}
}
}
On each request, the responseData
event is fired, enabling plugins to
inject configuration derived from the request being processed. It is passed the
gasket API, the public gasketData, and a context with the request & response.
Again, hooks should return a new object instead of mutating an existing object.
This is especially vital in the case of this event to avoid cross-request
information leaks.
Sample:
const getFeatureFlags = require('./feature-flags');
module.exports = {
hooks: {
async responseData(gasket, publicGasketData, { req, res }) {
const featureFlags = await getFeatureFlags({
shopperId: req.user.shopperId,
locale: req.cookies.market,
plid: req.user.resellerId
});
return {
...publicGasketData,
featureFlags
};
}
}
}