ES6 Modules in Browser

June 1, 2018
javascript es6 modules apprenticeship

For a cool dashboard application, I am using module type script loading. It’s a cool new feature and it offers a lot. This newly introduced feature, let’s you to import your ES6 modules directly. First of all, you don’t need to use any kind of bundler. On the other hand, since my scope is the only modern browsers, I don’t need to transpile my code. This removes my build process completely. You can observe changes as soon as you type them, no pre-process needed. It’s feels like the good old days which we can change the file and get the results right away. Also, it’s so simple, you just need to define the type as module;

<script type=module src="./index.js"></script>

Is there any catch? Yes, there is. This approach only works with relative paths for now. There are several ways to pass this problem. You can import your dependencies by the help of script tags or you can serve node_modules for dependencies. I am sure we will discover a best practice in the future.

Unfortunately, in my opinion it makes the project harder to tests. Since I can’t import a method directly like import a from 'a', I’ve needed to find a way to make my tests run in node environment. It’s impossible for node to understand scripts that I’ve imported via script tag in HTML. To overcome this problem, I’ve needed to wrap global scope and provide necessary dependencies for node and browsers. To do this, I’ve used the differences between them. Global object in NodeJS named as global and in the browsers, it is named as window. So, I wrote the following module;

const globalScopeFinder = () => {
  try {
    if (global) {
      return {
        window: {},
        Chartist: {},
        document: {},
        dateFns: require('date-fns')
      };
    }
  } catch (error) {
    return window;
  }
};

export default globalScopeFinder();

This code snippet helps me to understand the current environment. In the browser, if(global) line throws exception, then it returns window object as global scope. In the node environment, it doesn’t throw and returns the objects that I need for test purposes. Usage is as follows;

import globalScopeFinder from './global-scope-finder.js';
const { dateFns } = globalScopeFinder;

const getPreviousYear = (date) => dateFns.subWeeks(date, dateFns.getISOWeeksInYear(date));

export default getPreviousYear;

Well, this is not a great solution, but this trick made me capable of using the one of the newest features available.