Category Archives: JavaScript

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

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.

React + Recompose: Calling Multiple HOC Wrappers

PROBLEM

Sometimes, wrapping a React component with multiple High Order Components (HOC) can get rather unwieldy and unreadable.

For example:-

import React from 'react';
import { withRouter } from 'react-router-dom';
import { withStyles } from 'material-ui/styles';
import withWidth from 'material-ui/utils/withWidth';

class MyComponent extends React.PureComponent {
	// ...
}

export default withRouter(withStyles(styles)(withWidth()(MyComponent)));

SOLUTION

To fix this, we can leverage recompose library.

Now, we can rewrite the above example like this:-

import React from 'react';
import { withRouter } from 'react-router-dom';
import { withStyles } from 'material-ui/styles';
import withWidth from 'material-ui/utils/withWidth';
import compose from 'recompose/compose';

class MyComponent extends React.PureComponent {
	// ...
}

export default compose(
  withRouter,
  withStyles(styles),
  withWidth(),
)(MyComponent);

Keep in mind, the HOC order defined in compose(..) is important.

Webpack + ESLint: Automatically Fix ESLint Errors

PROBLEM

Given the following webpack.config.js

module.exports = {
  ...
  module: {
    rules: [
      {
        enforce: 'pre',
        test: /\.js?$/,
        loader: 'eslint-loader',
        exclude: /node_modules/,
      },
	  ...
    ],
  },
  ...
};

When running any Webpack command, ESLint may find violations and halt the entire process with the following error message:-

/path/to/front-end-stack/src/js/components/home/Home.js
  43:11  error  Expected indentation of 6 space characters but found 10  react/jsx-indent
  44:14  error  Expected indentation of 6 space characters but found 13  react/jsx-indent

x 2 problems (2 errors, 0 warnings)
  2 errors, 0 warnings potentially fixable with the `--fix` option.

SOLUTION

Certain errors (ex: trailing commas, wrong indentation, extra semicolon) are easily fixable.

There’s no need to halt the process and wait for developers to fix these obvious errors.

To configure ESLint to automatically fix these “soft” errors, add the following options block to the above rule:-

module.exports = {
  ...
  module: {
    rules: [
      {
        enforce: 'pre',
        test: /\.js?$/,
        loader: 'eslint-loader',
        exclude: /node_modules/,
        options: {
          fix: true,
        },		
      },
	  ...
    ],
  },
  ...
};

If you are using any VCS, remember to commit any file changes.

ES6 + Mocha + Sinon: Mocking Imported Dependency

PROBLEM

Let’s assume we have the following 2 files:-

apis.js

import fetch from 'isomorphic-fetch';

export const logout = () => (
  fetch('/logout')
    .then(resp => resp.json())
    .catch(err => err)
);

service.js

import { logout } from './apis';

export const kickUserOut = activeSession => (
  activeSession ? logout() : undefined
);

Let’s assume we want to test the logic in service.js without using nock to mock the HTTP call in apis.js.

While proxyquireify allows us to mock out the apis.js dependency in service.js, sometimes it is a little more complicated than needed.

SOLUTION

A simpler approach is to use sinon to stub out logout() defined in apis.js.

service-spec.js

import { beforeEach, afterEach, describe, it } from 'mocha';
import { expect } from 'chai';
import sinon from 'sinon';
import { kickUserOut } from './service';

// import everything as an object
import * as apis from './apis';

describe('service => kickUserOut', () => {
  let logoutStub;

  // before running each test, stub out `logout()`
  beforeEach(() => {
    logoutStub = sinon.stub(apis, 'logout').returns('success');
  });

  // after running each test, restore to the original method to
  // prevent "TypeError: Attempted to wrap logout which is already wrapped"
  // error when executing subsequent specs.
  afterEach(() => {
    apis.logout.restore();
  });

  it('given active session, should invoke logout API', () => {
    expect(kickUserOut(true)).to.deep.equal('success');
    expect(logoutStub.calledOnce).to.equal(true);
  });

  it('given expired session, should not invoke logout API', () => {
    expect(kickUserOut(false)).to.equal(undefined);
    expect(logoutStub.calledOnce).to.equal(false);
  });
});

Underscore.js: Introducing _.chain(…)

PROBLEM

Let’s assume we have the following JSON data:-

[
    {
        "date": "2015-07-30",
        "calendarAppointmentPtoJsonBeans": [
            {
                "basicEmployeeJsonBean": {
                    "id": 1,
                    "name": "Vrabel"
            	}
            }
        ]
    },
    {
        "date": "2015-07-31",
        "calendarAppointmentPtoJsonBeans": [
            {
                "basicEmployeeJsonBean": {
                    "id": 2,
                    "name": "Cray"
            	}
            },
            {
                "basicEmployeeJsonBean": {
                    "id": 1,
                    "name": "Vrabel"
            	}
            },
            {
                "basicEmployeeJsonBean": {
                    "id": 3,
                    "name": "Haeflinger"
            	}
            }
        ]
    }
]

What we want to do is to get all unique employees and ordered them by their names so that we get the following data:-

[
    {
        "id": 2,
        "name": "Cray"
    },
    {
        "id": 3,
        "name": "Haeflinger"
    },
    {
        "id": 1,
        "name": "Vrabel"
    }
]

SOLUTION 1: Less Elegant

Underscore.js provides various functions that allow us to pull this off.

var employees = _.sortBy( _.unique( _.pluck( _.flatten(
                _.pluck( jsonData, 'calendarAppointmentPtoJsonBeans' ) ),
                        'basicEmployeeJsonBean' ), function ( employee ) {
                    return employee.id;
                } ), function ( employee ) {
                    return employee.name;
                } );

While doable, the code is virtually not readable.

If you hate your peers and life, this is what you would write.

SOLUTION 2: More Elegant

The good news is Underscore.js also provides _.chain(..) that allows us to do the same thing through method chaining:-

var employees = _.chain( jsonData )
                .pluck( 'calendarAppointmentPtoJsonBeans' )
                .flatten()
                .pluck( 'basicEmployeeJsonBean' )
                .unique( function ( employee ) {
                    return employee.id;
                } )
                .sortBy( function ( employee ) {
                    return employee.name;
                } )
                .value();

Karma: Managing Plugin Versions in package.json

PROBLEM

Let’s assume our package.json looks like this:-

{
  "name": "testKarma",
  "private": true,
  "devDependencies": {
    "karma": "^0.12.24",
    "karma-chrome-launcher": "^0.1.4",
    "karma-coverage": "^0.2.4",
    "karma-jasmine": "^0.2.2",
    "karma-junit-reporter": "^0.2.1",
    "karma-phantomjs-launcher": "^0.1.4"
  }
}

What we want to do is to update all the plugin versions defined in this file.

SOLUTION

After trying out several solutions, it appears that using npm-check-updates is a better and cleaner solution for discovering newer versions of these plugins.

First, we need to install npm-check-updates globally. You may need to use sudo to do so.

sudo npm install -g npm-check-updates

Once installed, run the following command within the project root directory to discover any new versions:-

npm-check-updates

Output:-

"karma-chrome-launcher" can be updated from ^0.1.4 to ^0.1.5 (Installed: 0.1.5, Latest: 0.1.5)
"karma-coverage" can be updated from ^0.2.4 to ^0.2.6 (Installed: 0.2.6, Latest: 0.2.6)
"karma-jasmine" can be updated from ^0.2.2 to ^0.2.3 (Installed: 0.2.3, Latest: 0.2.3)
"karma-junit-reporter" can be updated from ^0.2.1 to ^0.2.2 (Installed: 0.2.2, Latest: 0.2.2)

Run 'npm-check-updates -u' to upgrade your package.json automatically

Finally, run the following command to update the plugins:-

npm-check-updates -u

Output:-

"karma-chrome-launcher" can be updated from ^0.1.4 to ^0.1.5 (Installed: 0.1.5, Latest: 0.1.5)
"karma-coverage" can be updated from ^0.2.4 to ^0.2.6 (Installed: 0.2.6, Latest: 0.2.6)
"karma-jasmine" can be updated from ^0.2.2 to ^0.2.3 (Installed: 0.2.3, Latest: 0.2.3)
"karma-junit-reporter" can be updated from ^0.2.1 to ^0.2.2 (Installed: 0.2.2, Latest: 0.2.2)

package.json upgraded

The plugin versions are successfully updated, and package.json is also updated accordingly.

{
  "name": "testKarma",
  "private": true,
  "devDependencies": {
    "karma": "^0.12.24",
    "karma-chrome-launcher": "^0.1.5",
    "karma-coverage": "^0.2.6",
    "karma-jasmine": "^0.2.3",
    "karma-junit-reporter": "^0.2.2",
    "karma-phantomjs-launcher": "^0.1.4"
  }
}