Created
June 7, 2017 22:28
-
-
Save heyimalex/ec48d03d8c9ab581b9ebe88e2b6ec093 to your computer and use it in GitHub Desktop.
create-react-app build script with optional runtime env var injection
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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