SPFx - Learning And Findings - Part Two


Hello everyone, this is part 2 of my series. For the first one, you can check out  SPFX - Learning and Findings - Part One.

Environment Update During Builds

In this article, we will learn how to make environment-based changes dynamically. I was using CDN in my application and as you know the URL changes for each environment so before creating a build I must make changes everywhere.
This was boring, so I thought of automating it. I started Googling it and found plugins that can do these changes on Json files but what happens if there is something in code (i.e. typescript files.)?
For JSON changes I got inspired by Wictor Wilen Article SharePoint Framework bundles for multiple tenants. Thanks to him, I was able to write JSON modification logic.
Before we go in detail, you should know SPFx uses “webpack” as a module bundler and “gulp” for tasks. For more details about these, you can Google it. In the latest version of SPFx 1.10.0, they use webpack 4+. Webpack 4+ has few inbuilt plugins like define plugin. This define plugin will help us in defining global constants. For modifications in the JSON file, we will create a task which is like gulp task and add in the SPFx pipeline.
This code has to be added in gulp file before “build.Initialize(gulp);”.
  1. //Define this variables at the top of the file  
  2. var cdnurl = "";  
  3. var tinymcecdnurl=""  
  4. var gulpenvsetting="local";  
  5. //Following function initialise the environment  based on parameter specified in terminal  
  6. const getCDNUrl = build.subTask('getcdnurl', (gulp, buildConfig, done) => {  
  7.     //Getting the environment parameter from terminal  
  8.     const targetval = buildConfig.args['target-env'];  
  9.     //checking if environment parameter is present otherwise it will take default  
  10.     if (targetval) {  
  11.       gulpenvsetting = targetval;  
  12.     }  
  13.     //based on env cdn url is set.You can do other operations also.  
  14.     if (gulpenvsetting == "local") {  
  15.       cdnurl = "https://publiccdn.sharepointonline.com/<<url>>";  
  16.     }  
  17.     else if (gulpenvsetting == "qa") {  
  18.       cdnurl = "https://publiccdn.sharepointonline.com/<<url>>";  
  19.     }  
  20.     else if (gulpenvsetting == "prod") {  
  21.       cdnurl = "https://publiccdn.sharepointonline.com/<<url>>";  
  22.     }  
  23.     tinymcecdnurl = cdnurl + '/tinymce/tinymce.min.js';  
  24.     done();  
  25.   });  
  27.   //Following function replaces url in config.json  
  28.   const replaceCDNUrlsInConfig = build.subTask('replacecdnurl', (gulp, buildConfig, done) => {  
  29.     //Other operations can be done here based on your requirements.  
  30.     //parsing the config.json externals  
  31.     for (const key in buildConfig.properties.externals) {  
  32.       var external = buildConfig.properties.externals[key];  
  33.       //replacing the dummystring mentioned in the config.json with our actual value  
  34.       if (external.path) {  
  35.         external.path = external.path.replace(dummyurl, cdnurl);  
  36.       }  
  37.       else {  
  38.         buildConfig.properties.externals[key] = external.replace(dummyurl, cdnurl);  
  39.       }  
  41.     }  
  42.     done();  
  43.   });  
  44.   //Following function merge webpackconfig with our configuration.  
  45.   //In this function we define global constants which will be used in TS file.  
  46.   build.configureWebpack.mergeConfig({  
  47.     additionalConfiguration: (generatedConfiguration) => {  
  48.       for (var i = 0; i < generatedConfiguration.plugins.length; i++) {  
  49.         var plugin = generatedConfiguration.plugins[i];  
  50.         if (plugin instanceof webpack.DefinePlugin) {  
  51.             //defining global constant "tinymcecdnurl"  
  52.           plugin.definitions.tinymcecdnurl = JSON.stringify(tinymcecdnurl);  
  53.           break;  
  54.         }  
  55.       }  
  56.       return generatedConfiguration;  
  57.     }  
  58.   });  
  59.   //Task create above has to be added in SPFx pipeline.Following is the way to add in pipeline.  
  60.   build.rig.addPreBuildTask(getCDNUrl);  
  61.   build.rig.addPostBuildTask(replaceCDNUrlsInConfig);  
I have already added comments in the code which will help you out. As you can see, I have pasted the actual code and not the whole file. Variables are defined at the top in gulp as per standard, but you can define anywhere. Each function can be defined as a task in gulp, so we define two functions, one for initializing environment variables based on a single parameter and another function that changes the properties. We can do other operations based on our requirements.
Now, if you see in the end, I have added this function as a task in the SPFx pipeline. If you type “build.rig.” you will be presented with a lot of options. I have used the ones which I found suitable, but you can use others depending upon your requirement. ”getCDNUrl” is an independent function which just needs input from the terminal so I have executed this as first and added as "addPreBuildTask". Once I initialize all the env settings I have added replacement logic in “addPostBuildTask”. This gets executed just before the webpack execution starts.
For replacement in files, you must define global constants that a webpack can replace later during bundling. This global constant is initialized using the Define plugin. MergeConfig and DefinePlugin are inbuilt in webpack so you don’t need to download any other package.
We will see how we use this in our files. First, let us see how to use global constants. There can be other ways, but with the following way that  I am using in my code, during compile time the webpack will be replaced with an actual string.
  1. var TinyMCECdnUrl = `${tinymcecdnurl}`;  
Config.json sample 
  1. "externals": {  
  2.     "moment""https://<<dummystring>>/moment.js"  
  3.   }  
As you can see, I have used a dummy string that will be replaced during compile time.
Now, from the terminal, you can write the below compile command, and accordingly, your code will have the env settings:
  • gulp build --target-env qa/prod/local
  • gulp bundle --target-env qa/prod/local --ship
  • gulp package-solution --target-env qa/prod/local –ship
Please let me know how you liked this article. I will post another article in the series soon.