Wait. What?
You’re probably thinking – “WHY.” It’s simple, really.
- Historically, I’ve been a Microsoft stack developer. C#, SQL, Azure… the whole 9 yards. I’m very comfortable with the Microsoft stack. I already store my repos in AzureDevops.
- I wanted to ramp up a project quickly to learn React.
- A friend told me, “Dude, you have to go with Firebase for React”
So, here we are.
Continuous Integration/Continuous Deployment Goals
I have some straightforward goals to achieve:
- Have a development and production branch (DEV and MASTER, respectively)
- Make changes on DEV branch. Push should build and deploy to my development instance
- Merge DEV into MASTER. Push should build and deploy to my production instance
- If possible, I want only one pipeline to handle both (yeah, yeah, this probably won’t scale)
- Don’t build or deploy feature branches
Assumptions and Prerequisites
This article assumes a lot of things. I assume that you:
- Have a React app that was created with create-react-app or at least meets Firebase’s deploy requirements
- Have a Firebase account and some code you’re going to deploy
- Have two Firebase projects – one for your development build and one for your production build.
- Have installed and can use the Firebase CLI
- Have an Azure DevOps account and can get code into your repo there
- Know how to use git and repos and how to push your code to remotes
- Aren’t afraid to try some of this out if you don’t have any of the above
Steps
I followed Victor Bruce-Crabbe’s “How to deploy a react-app to different firebase hosting environments(dev and prod)” to set up my project for dev and production builds. Once I had that in place, I used Microsoft’s Build, test, and deploy Javascript and Node.js apps document to figure out the rest.
The entire setup hinges on defining environment set ups in your code, then creating an Azure Pipelines yaml file that is added to your repo.
Code Prep
Make sure all your changes are pushed/stashed/whatever. You should start with no pending changes.
Create and Use Environment Variables
You can follow the article here for more in-depth instructions. I’m just going to hit the main points here.
Install env-cmd
Install env-cmd in your project. The env-cmd package lets you group variables into an environment file (.env) and pass them to your script.
npm install env-cmd
Separate Your REACT_APP Variables
In your project root, create two files that contain variables for the respective environments. You’re going to put the web-app configuration values into these files and check them in. Yes, check them in. Victor’s instructions say otherwise, but the values you put in here are in your Firebase Console > Project Overview > Project Settings and would have been pasted into your HTML anyway. Just don’t put other secrets here (like other API keys or secrets). I’m creating another article for that later…
The files you need to create at the root of your project are:
- .env.development
- .env.production
In both files I added a REACT_APP_ENV_ID variable, set to the appropriate value (PRD or DEV). So, in .env.development, I it looks like:
REACT_APP_ENV_ID=DEV
In my file where I initialize the Firebase client, I did the following:
import app from 'firebase/app';
const prodConfig = {
apiKey: process.env.REACT_APP_PROD_API_KEY,
authDomain: process.env.REACT_APP_PROD_AUTH_DOMAIN,
databaseURL: process.env.REACT_APP_PROD_DATABASE_URL,
projectId: process.env.REACT_APP_PROD_PROJECT_ID,
storageBucket: process.env.REACT_APP_PROD_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_PROD_MESSAGING_SENDER_ID,
appId: process.env.REACT_APP_PROD_APP_ID,
measurementId: process.env.REACT_APP_PROD_MEASUREMENT_ID,
};
const devConfig = {
apiKey: process.env.REACT_APP_DEV_API_KEY,
authDomain: process.env.REACT_APP_DEV_AUTH_DOMAIN,
databaseURL: process.env.REACT_APP_DEV_DATABASE_URL,
projectId: process.env.REACT_APP_DEV_PROJECT_ID,
storageBucket: process.env.REACT_APP_DEV_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_DEV_MESSAGING_SENDER_ID,
appId: process.env.REACT_APP_DEV_APP_ID,
measurementId: process.env.REACT_APP_DEV_MEASUREMENT_ID,
};
const config = process.env.REACT_APP_ENV_ID === 'PRD' ? prodConfig : devConfig;
class Firebase {
constructor() {
app.initializeApp(config);
/* moar code */
}
}
export default Firebase;
Update package.json
In your root package.json file, in the scripts node, add build scripts for build:development and build:production. Mine look like this:
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"build:development": "env-cmd -f .env.development react-scripts build",
"build:production": "env-cmd -f .env.production react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
Save and Test
Save and test to make sure that everything builds and works correctly.
npm start | This command should start your code in debug mode locally |
npm build:development | This command should build optimized code using your dev configuration |
npm build:production | This command should build optimized code using your prod configuration |
Check that running these commands does what you expect. Check in and push.
Firebase Prep
Make sure you’ve initialized firebase in your project directory.
Follow the Firebase CLI reference to get a refresh token. Your pipeline needs this to deploy.
.firebaserc
Check your .firebaserc file. It should have your production and development environments in it. You can manually edit or use the CLI to both firebase use development and firebase use production
In my case, my development project is the default.
{
"projects": {
"default": "<<development-project-name>>",
"production": " <<production-project-name>> ",
"development": " <<development-project-name>> "
}
}
From the Firebase CLI reference:
In general, you should check your
.firebaserc
file into source control to allow your team to share project aliases. However, for open source projects or starter templates, you should generally not check in your.firebaserc
file.https://firebase.google.com/docs/cli#project_aliases
Azure DevOps Pipeline
- Create a new Pipeline.
- Select your repo location.
- Select your repo.
- Select Node.js as the pipeline.
You’re provided with default yaml. - Click the Variables button.
The New Variable flyout opens. - Click New Variable.
- Use the name FIREBASE_TOKEN and paste your refresh token into the Value field.
- Check the Keep this value secret checkbox.
- Click OK.
- Replace the default yaml with the code below.
- If your branches aren’t named master or dev, make changes under trigger.
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
trigger:
- master
- dev
pool:
vmImage: 'ubuntu-latest'
variables:
${{ if eq(variables['Build.SourceBranchName'], 'master') }}: # master
buildToken: 'production'
${{ if eq(variables['Build.SourceBranchName'], 'dev') }}: # dev
buildToken: 'development'
steps:
- task: NodeTool@0
inputs:
versionSpec: '12.x'
displayName: 'Install Node.js'
- script: |
npm install -g firebase-tools
displayName: 'Install firebase tools'
- script: |
npm install env-cmd --save
displayName: 'Install env-cmd'
- script: |
npm install
cd functions && npm install
cd ..
npm run build:$(buildToken)
displayName: 'npm install and build $(buildToken)'
- script: |
firebase deploy -P $(buildToken) --token "$(FIREBASE_TOKEN)"
displayName: 'Deploy to firebase $(buildToken)'
NOTE: This script assumes that you have functions. If you don’t have functions, remove the following two lines in the third script block
cd functions && npm install
cd ..
The Moment of Truth
Click the Save and Run button.
Here’s where things get a little fuzzy, because I actually did this a while ago. If I remember correctly, the wizard checks the file into the root of your repo. Hopefully, you get to choose which repo so it goes into the dev branch.
If the Save and Run process pushes your dev branch successfully, then you’re almost there!
Pull your dev branch, since you added an azure-pipelines.yml file. Make a small change and push; your dev branch should build and be pushed to Firebase development hosting.
Assuming everything tests well, merge your dev branch into your master branch, then push and your production branch should build and be pushed to your Firebase production hosting.