Skip to content

Instantly share code, notes, and snippets.

@heyimalex
Created June 7, 2017 22:28
Show Gist options
  • Save heyimalex/ec48d03d8c9ab581b9ebe88e2b6ec093 to your computer and use it in GitHub Desktop.
Save heyimalex/ec48d03d8c9ab581b9ebe88e2b6ec093 to your computer and use it in GitHub Desktop.
create-react-app build script with optional runtime env var injection
const fs = require("fs");
const crypto = require("crypto");
const spawnSync = require("child_process").spawnSync;
// Load the environment using the same code react-scripts uses.
process.env.NODE_ENV = "production";
const clientEnv = require("react-scripts/config/env")().raw;
const REACT_APP_RUNTIME = /^REACT_APP_RUNTIME_/i;
// Filter out the REACT_APP_RUNTIME variables and a generate random
// token for each of them. Also keep around the actual values for them
// to be used as defaults.
const injections = Object.keys(clientEnv)
.filter(key => REACT_APP_RUNTIME.test(key))
.map(key => ({
name: key,
default: clientEnv[key],
token: `v${crypto.randomBytes(24).toString("hex")}`
}));
if (injections.length === 0) {
throw new Error("no runtime variables to inject.");
}
const injectedEnv = injections.reduce((env, i) => {
env[i.name] = i.token;
return env;
}, {});
// Fix issue with calling npm run on windows, idk.
injectedEnv.APPDATA = process.env.APPDATA;
// Run the build process with the REACT_APP_RUNTIME_ variables set to
// their token values.
spawnSync("npm", ["run", "build"], {
shell: true,
stdio: [0, 1, 2],
env: injectedEnv
});
// Read the asset manifest to grab the path to the main js file.
const assetManifest = require("./build/asset-manifest.json");
const mainjsPath = `./build/${assetManifest["main.js"]}`;
// Find and replace all of the token strings with window-bound variable
// references.
// TODO: This will mess up the main.js sourcemap, and also the file
// hash won't technically be correct anymore. Also I haven't kept up
// with cra's new features, so maybe there are more places that env
// vars end up and need to be changed?
let mainjs = fs.readFileSync(mainjsPath, "utf8");
injections.forEach(i => {
mainjs = mainjs.replace(
new RegExp(JSON.stringify(i.token), "g"),
`window.${i.token}`
);
});
fs.writeFileSync(mainjsPath, mainjs);
const indexRaw = fs.readFileSync("./build/index.html", "utf8");
// Build the generator script and save it.
const genIndex = `
const env = [
${injections
.map(
i => `{
token: ${JSON.stringify(i.token)},
value: process.env[${JSON.stringify(i.name)}] || ${JSON.stringify(
i.default
)}
}`
)
.join(",\n ")},
];
let index = ${JSON.stringify(indexRaw)};
const definitions = [];
env.forEach(e => {
index = index.replace(new RegExp(e.token, 'g'), e.value);
definitions.push('window.' + e.token + '=' + JSON.stringify(e.value) + ';');
})
index = index.replace(
'<head>',
'<head><script>' + definitions.join('') + '</script>'
);
require('fs').writeFileSync(
require('path').join(__dirname, './index.html'),
index
);
`;
fs.writeFileSync("./build/gen-index.js", genIndex);
// Remove the original index file so it's clear that it needs to be
// generated.
fs.unlinkSync("./build/index.html");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment