Webpack: Managing Tree Shaking

PROBLEM

Sometimes, Webpack’s tree-shaking may accidentally eliminate imported code from import statements.

For example, we may have a root JS file that imports a CSS file:-

import React from 'react';
import ReactDOM from 'react-dom';
import '../css/index.css';

...

… and for some reason, the CSS file will never appear in the final bundle even if the Webpack config contains proper rules to handle CSS files.

SOLUTION

To prevent Webpack from removing any “unreferenced” code (ex: global CSS files, JS polyfills, etc), list these side effects under package.json, for example:-

{
  "name": "front-end-stack",
  "sideEffects": [
    "*.css"
  ]
}
Advertisements

Azure: Deploying WAR File to Tomcat

PROBLEM

Typically, when using ZipDeploy to push a WAR file (ex: my-app.war) to an Azure instance, we need to:-

  • Rename my-app.war to ROOT.war.
  • Place ROOT.war under webapps/.
  • Zip up webapps/.
  • Use ZipDeploy to push the zip file to an Azure instance.

This zip file will automatically be unzipped under site/wwwroot:-

D:\home\site\wwwroot
└── webapps
    └── ROOT.war

Tomcat detects ROOT.war and will try to unpack the WAR file under ROOT/:-

D:\home\site\wwwroot
└── webapps
    ├── ROOT
    │   ├── META-INF
    │   │   └── ...
    │   └── WEB-INF
    │       └── ...
    └── ROOT.war

The problem is sometimes Tomcat is unable to fully unpack ROOT.war because some files may be locked by some running processes. As a result, the web page fails to load and shows 404 error.

SOLUTION

A better and more stable solution is to use WarDeploy to push WAR file to an Azure instance because:-

  • It simplifies the deployment process because we don’t need to create a zip file with webapps/ containing a WAR file named ROOT.war.
  • Instead of relying on Tomcat to unpack the WAR file, WarDeploy will do the unpacking step elsewhere before copying to site/wwwroot/webapps/.

To use WarDeploy, you can use curl command or PowerShell script to do so.

Here’s an example of the PowerShell script:-

Param(
    [string]$filePath,
    [string]$apiUrl,
    [string]$username,
    [string]$password
)

$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username, $password)))

Invoke-RestMethod -Uri $apiUrl -Headers @{ Authorization = ("Basic {0}" -f $base64AuthInfo) } -Method POST -InFile $filePath -ContentType "multipart/form-data"

If the PowerShell script above is called deploy.ps1, to push my-app.war to my-app Azure instance:-

deploy.ps1 -filePath D:\my-app.war -apiUrl https://my-app.scm.azurewebsites.net/api/wardeploy -username [USER] -password [PASSWORD]

Depending on the size of the WAR file, you may get “The operation has timed out” error after 100 seconds:-

Invoke-RestMethod : The operation has timed out
At D:\agent\_work\2ea6e947a\my-app\deploy.ps1:18 char:1
+ Invoke-RestMethod -Uri $apiUrl -Headers @{Authorization=("Basic {0}"  ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

This will most likely to occur if the WAR file is too big (80MB+).

To fix this, increase the time out duration by adding -TimeoutSec option to Invoke-RestMethod statement:-

Invoke-RestMethod [OTHER_OPTIONS...] -TimeoutSec 300

Feign + Eureka: UnknownHostException when Attempting to Invoke a Service

PROBLEM

When attempting use Feign to invoke a service through Eureka, the following exception occurs:-

MY-HOST-NAME executing GET http://donkey-kong-service/throw/barrels/10
feign.RetryableException: MY-HOST-NAME executing GET http://donkey-kong-service/throw/barrels/10
	at feign.FeignException.errorExecuting(FeignException.java:84)
	at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:113)
	...
Caused by: java.net.UnknownHostException: MY-HOST-NAME
		at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:184)

SOLUTION

Go to the donkey-kong-service app and add the following line to the application.properties:-

eureka.instance.prefer-ip-address=true

Now, when Feign tries to invoke the service, the IP address will be used instead of the OS’ reported hostname.

Spring Boot: Restarting App using Dev Tools with IntelliJ IDEA

Spring Boot provides spring-boot-devtools module that allows the app to “smartly” restart whenever the files on the classpath have changed.

Because the rarely changed classes (ex: 3rd party JARs) are separated out into a different classloader from the app’s actively developed classes’ classloader, it allows Spring Boot to quickly restart the app compared to “cold start”.

DEPENDENCY

First, add the following dependency:-

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-devtools</artifactId>
	<scope>runtime</scope>
</dependency>

CONFIGURING INTELLIJ IDEA

In IntelliJ IDEA:-

  • Click SHIFT twice to bring up the “Search History” dialog.
  • Select “Actions” tab.
  • Type “Registry” in the search box.
  • Select “Registry…”.

In the “Registry” dialog:-

  • Find “compiler.automake.allow.when.app.running” key.
  • Check the checkbox.
  • Close the dialog.

In IntelliJ IDEA “Preferences” dialog:-

  • Go to “Build, Execution, Deployment” » “Compiler”.
  • Check “Build project automatically”.
  • Close the dialog.

Finally, instead of running Maven goals to run the Spring Boot app, select the Application class (annotated with @SpringBootApplication) and run it from IntelliJ IDEA.

Anytime the app’s class files have changed, IntelliJ IDEA will compile the app, which will then trigger Spring Boot Dev Tools to restart the app.

Spring Boot: Connecting to IBM MQ over JMS using non-IBM JRE

There are several ways to connect to IBM MQ:-

  • com.ibm.mq.MQQueueManager
  • com.ibm.mq.jms.MQQueueConnectionFactory
  • com.ibm.msg.client.jms.JmsConnectionFactory

This article shows you how to connect with Spring’s JmsTemplate.

CONNECTIVITY INFO

Typically, the MQ admin will provide the following connectivity info that allows you to connect to MQ:-

  • Queue manager name, ex: MY.QUEUE.MANAGER
  • Host name, ex: server.com
  • Port, ex: 1415
  • Channel name, ex: MY.SSL.CHANNEL
  • SSL Cipher Suite, ex: SSL_ECDHE_RSA_WITH_AES_128_GCM_SHA256
  • User, ex: user
  • Password, ex: password
  • Queue name, ex: MY.QUEUE

DEPENDENCY

Add the following dependency:-

dependencies {
  compile 'com.ibm.mq:mq-jms-spring-boot-starter:2.1.1'
}

SPRING CONFIGURATION

While the connectivity info can be hardcoded in Spring Boot’s application.properties, it’s probably more logical to use Spring @Configuration to dynamically set the values especially dealing with credential.

So, create a Spring configuration that looks something like this:-

// while all the value are hardcoded here for simplicity sake, you can inject
// sensitive values from DB, through <code>Environment</code>, etc.
@EnableJms
@Configuration
class JmsConfig {
  // Adding @Primary will force Spring Boot to use this bean
  // instead of the one provided by the dependency 
  @Primary
  @Bean
  MQConfigurationProperties mqConfigurationProperties() {
    System.setProperty('javax.net.ssl.keyStore', '/path/to/keystore.jks')
    System.setProperty('javax.net.ssl.keyStorePassword', 'XXXXXXX')

    return new MQConfigurationProperties(
      queueManager: 'MY.QUEUE.MANAGER',
      channel: 'MY.SSL.CHANNEL',
      connName: 'server.com(1415)',
      user: 'user',
      password: 'password',
      userAuthenticationMQCSP: true,
				
      // If the provided SSL cipher suite begins with "SSL", 
      // replace it with "TLS" instead.
      // SSL_* is IBM JRE CipherSuite name.
      // TLS_* is Oracle JRE CipherSuite name. 
      sslCipherSuite: 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256',
				
      // true - if using IBM JRE
      // false - if using non-IBM JRE, ex: Oracle, OpenJDK, etc
      useIBMCipherMappings: false
    )
  }
}

USING JMS TEMPLATE

Finally, to listen to the given queue, it is as easy as autowiring JmsTemplate and start using it.

@Autowired
JmsTemplate jmsTemplate

...

final Message message = jmsTemplate.receive('MY.QUEUE')
println message

React: Debugging Layout Thrashing

PROBLEM

When the React app grows larger over time, it is highly likely to run into situations where the component keeps re-rendering for no apparent reason.

There are many reasons why this is happening… to name a few…. parent component re-renders, this.props or this.state has changed, etc.

The key is to quickly find out what causes the component to re-render constantly.

SOLUTION

The simplest solution, in my opinion, is to paste the following block into the troublesome component:-

componentDidUpdate(prevProps, prevState) {
  const debug = (label, currObject = {}, prevObject = {}) => {
    Object.entries(currObject).forEach(([key, currValue]) => {
      if (prevObject[key] !== currValue) {
        console.log(`[DEBUG] ${label} has changed: `, key);
        console.log('[DEBUG] - BEFORE : ', prevObject[key]);
        console.log('[DEBUG] -  AFTER : ', currValue);
      }
    });
  };

  debug('Prop', this.props, prevProps);
  debug('State', this.state, prevState);
}

Adding this componentDidUpdate(..) lifecycle allows us to quickly find out which property or state has changed.

When running the app, the console may display something like this:-

Console Log

The key is to look for identical object or array displayed in both “BEFORE” and “AFTER” statements. This shows that while the values look similar, they fail on strict equality check (‘===’), which causes the component to re-render.

Groovy/Java: Handling Byte Order Marks When Reading a File

PROBLEM

Given a file with the following content:-

10,20

When reading the file:-

def inputStream = new FileInputStream('test.csv')
def value = inputStream.text.trim()

println "|${value}|"

… the following values are printed:-

| 10,20|

Even though the value is trimmed, there is still a leading space in front of text.

A further inspection reveals the leading space is not a regular space:-

// first character is not a space
assert value.charAt(0) != (char) ' '

// ASCII value: 65279 vs 32
assert (int) value.charAt(0) != (int) ((char) ' ').charValue()

SOLUTION

Some editors prepend a special Unicode character called a byte order mark (BOM) to the file.

The simplest way to remove this special character is to leverage Apache Commons IO’s BOMInputStream:-

def inputStream = new BOMInputStream(new FileInputStream('test.csv'))
def value = inputStream.text.trim()

println "|${value}|"

… and now, the values are printed correctly:-

|10,20|