Transforming our JavaScript code at build-time with Babel

A few months ago, we started using ES6 template strings to embed fragments of HTML and CSS in our Web SDK. Template strings allowed us to inline multi-line content and expression interpolation served all our templating needs. We used Babel of course, to transpile these template strings to regular strings in the build, since we want our code to run even on browsers that don’t support template strings.

Now the transpilation preserves all whitespace characters it encounters in the template string as-is which means all the newlines, tabs and extra spaces that were used to indent and format the HTML and CSS fragments for code readability, would show up in the output and unnecessarily bloat the shipped code. Since these whitespace characters were components of strings, even minification or any text processing couldn’t remove them (how were we to distinguish strings containing the fragments from the others). We needed a point in our build process where we could smartly detect template strings containing the fragments and strip them of unnecessary whitespace. Luckily we discovered babel-plugin-dedent and used that as a reference to write our own whitespace removal Babel plugin.

A Babel plugin is most often written for source code transformations e.g. converting the ES6 arrow functions to ES5 compatible syntax. However, there are other uses as well, such as static analysis of source code. Plugins only need to work on the transformations, Babel takes care of the hard parts – parsing your source code, constructing an abstract syntax tree (AST), traversing that AST, tracking all the transformations that plugins make on the AST and finally generating a source code out of the transformed AST for output.

A Babel plugin is simply a function returning an object of visitor methods. The names of these visitor methods indicate the type of AST nodes they want to process. While traversing the AST wherever Babel encounters a node of such a type, it calls this method passing it a path object.

Example of a basic Babel plugin (written as a CommonJS module)

module.exports = function (babel) {
  var t = babel.types;
 
  return {
    visitor: {
      BinaryExpression: function (path) {
        // *path* contains the visited node along with related nodes,
        // metadata and some utility methods
        console.log("Operator of binary expression is", path.node.operator);
      }
    }
  };
};

Coming back to our whitespace removal plugin, we had to tag our template strings containing HTML and CSS fragments in the source code. This was needed to easily identify template strings with insignificant whitespace (where extra whitespaces can be removed) later on in the build process. The name of the tag was chosen to be
nowhitespace

We wrote our plugin to visit TaggedTemplateExpression type nodes, use babel.types to check if the tag name was nowhitespace and perform the transformation i.e. remove extra whitespaces from the various parts of the template string (called quasis). We also had to remove the tag from the template string, so we simply replace the TaggedTemplateExpression node with the inner TemplateLiteral node. Plugins in babel-preset-es2015 then took care of converting TemplateLiteral nodes to a regular string.

Below’s a version of the plugin (whitespace removal code is simplified). You can see it in action at astexplorer.net.

var pattern = new RegExp("[\n\t ]+", "g");
 
function transfrom (quasis) {
  ["raw", "cooked"].forEach(function (type) {
    quasis.forEach(function (element) {
      element.value[type] = element.value[type].replace(pattern, " ");
    });
  });  
}
 
module.exports = function (babel) {
  var t = babel.types;
 
  return {
    visitor: {
      TaggedTemplateExpression: function (path) {
        let node = path.node;
 
        if (t.isIdentifier(node.tag, { name: "nowhitespace" })) {
          transfrom(node.quasi.quasis);
          return path.replaceWith(node.quasi);
        }
      }
    }
  };
};

We configured Babel to use our plugin in the build process. One can put the plugin module path in .babelrc. When Babel is used through its API, the plugin’s function can directly be passed in the plugins option

Since the first working iteration, we have generalized this plugin to do proper minification of the fragments using html-minifier and cssmin. Check out the plugin code on GitHub (note that it is still experimental).

Babel’s plugin ecosystem is growing day after day on npm. You’ll find many of them serve niche, custom use cases.

To aspiring plugin authors, we recommend reading up on the Babel Plugin Handbook by James Kyle. Also, you will find astexplorer.net quite handy in inspecting ASTs and trying out transformations.

Thanks for stopping by and happy transpiling.

'Coz sharing is caring

SELinux

Security-Enhanced Linux (SELinux) is a security architecture integrated into the 2.6.x kernel using the Linux Security Modules (LSM).

SELinux Overview

SELinux provides a flexible Mandatory Access Control (MAC) system built into the Linux kernel. Under standard Linux Discretionary Access Control (DAC), an application or process running as a user (UID or SUID) has the user’s permissions to objects such as files, sockets, and other processes. Running a MAC kernel protects the system from malicious or flawed applications that can damage or destroy the system.

SELinux defines the access and transition rights of every user, application, process, and file on the system. SELinux then governs the interactions of these entities using a security policy that specifies how strict or lenient a given Red Hat Enterprise Linux installation should be.

On a day-to-day basis, system users will be largely unaware of SELinux. Only system administrators need to consider how strict a policy to implement for their server environment. The policy can be as strict or as lenient as needed and is very finely detailed. This detail gives the SELinux kernel complete, granular control over the entire system.

The SELinux Decision Making Process

When a subject, (for example, an application), attempts to access an object (for example, a file), the policy enforcement server in the kernel checks an access vector cache (AVC), where subject and object permissions are cached. If a decision cannot be made based on data in the AVC, the request continues to the security server, which looks up the security context of the application and the file in a matrix. Permission is then granted or denied, with an avc: denied message detailed in /var/log/messages if permission is denied. The security context of subjects and objects is applied from the installed policy, which also provides the information to populate the security server’s matrix.


SELinux Decision Process

SELinux Operating Modes

Instead of running in enforcing mode, SELinux can run in permissive mode, where the AVC is checked and denials are logged, but SELinux does not enforce the policy. This can be useful for troubleshooting and for developing or fine-tuning SELinux policy.

The /selinux/ pseudo-file system contains commands that are most commonly used by the kernel subsystem. This type of file system is similar to the /proc/ pseudo-file system. Administrators and users do not normally need to manipulate this component.

There are two ways to configure SELinux under Red Hat Enterprise Linux: using the SELinux Administration Tool (system-config-selinux), or manually editing the
 primary configuration file (/etc/sysconfig/selinux).

The /etc/sysconfig/selinux contains a symbolic link to the actual configuration file, /etc/selinux/config.

SELinux Configuration

SELINUX=enforcing|permissive|disabled — Defines the top-level state of SELinux on a system.

  • enforcing — The SELinux security policy is enforced.
  • permissive — The SELinux system prints warnings but does not enforce policy.This is useful for debugging and troubleshooting purposes. In permissive mode, more denials are logged because subjects can continue with actions that would otherwise be denied in enforcing mode. For example, traversing a directory tree in permissive mode produces avc: denied messages for every directory level read. In enforcing mode, SELinux would have stopped the initial traversal and kept further denial messages from occurring.
  • disabled — SELinux is fully disabled. SELinux hooks are disengaged from the kernel and the pseudo-file system is unregistered.

SELINUXTYPE=targeted|strict — Specifies which policy SELinux should enforce.

  • targeted — Only targeted network daemons are protected.
  • strict — Full SELinux protection, for all daemons. Security contexts are defined for all subjects and objects, and every action is processed by the policy enforcement server.
'Coz sharing is caring