Understanding JavaScript Compilation

@MicheleRivaCode

👋 Hi there! I'm Michele

🧑‍💻 Senior Architect at NearForm

📗 Author of Real-World Next.js

🏅 Google Developer Expert & Microsoft MVP

@MicheleRivaCode

compilation transpilation

@MicheleRivaCode

compilation

@MicheleRivaCode

#include <stdio.h>

int main() {
   printf("Hello, World!");
   return 0;
}

transpilation

@MicheleRivaCode

module Button = {
  @react.component
  let make = (~count: int) => {
    let times = switch count {
    | 1 => "once"
    | 2 => "twice"
    | n => Belt.Int.toString(n) ++ " times"
    }
    let msg = "Click me " ++ times

    <button> {msg->React.string} </button>
  }
}

input

transpilation

@MicheleRivaCode

// Generated by ReScript, PLEASE EDIT WITH CARE
'use strict';

var React = require("react");

function Playground$Button(Props) {
  var count = Props.count;
  var times = count !== 1 ? (
      count !== 2 ? String(count) + " times" : "twice"
    ) : "once";
  var msg = "Click me " + times;
  return React.createElement("button", undefined, msg);
}

var Button = {
  make: Playground$Button
};

exports.Button = Button;
/* react Not a pure module */

output

transpilation

@MicheleRivaCode

const user = {
  name: {
    first: "Michele",
    last: "Riva"
  }
};

console.log(user?.name?.middle ?? "No middle name");

input

transpilation

@MicheleRivaCode

"use strict";

var _user$name$middle, _user$name;

const user = {
  name: {
    first: "Michele",
    last: "Riva"
  }
};
const middleName = (_user$name$middle = user === null || user === void 0
? void 0
: (_user$name = user.name) === null || _user$name === void 0 ? void 0
: _user$name.middle) !== null && _user$name$middle !== void 0
? _user$name$middle : "No middle name";
console.log(middleName);

output

@MicheleRivaCode

Parsing

Transformation

Codegen

@MicheleRivaCode

Parsing

Step 1

Tokenization

var foo = 10
var
foo
=
10

input

tokens

@MicheleRivaCode

Parsing

Step 2

Syntactic Analysis

var foo = 10
            =
           / \
          /   \
        var   10
         |
        foo

input

parse tree
(aka concrete syntax tree)

@MicheleRivaCode

Parsing

Step 2

Syntactic Analysis

var foo = 10
       variableDeclaration
               |
               |
       vairiableDeclarator
          /         \
         /           \
   Identifier   NumericLiteral

input

abstract syntax tree (AST)

@MicheleRivaCode

@MicheleRivaCode

{
   "body":[
      {
         "type":"VariableDeclaration",
         "declarations":[
            {
               "type":"VariableDeclarator",
               "id":{
                  "type":"Identifier",
                  "name":"foo",
                  "loc":{
                     "identifierName":"foo"
                  }
               },
               "init":{
                  "type":"NumericLiteral",
                  "extra":{
                     "rawValue":10,
                     "raw":"10"
                  },
                  "value":10
               }
            }
         ],
         "kind":"var"
      }
   ]
}

@MicheleRivaCode

Traversing the AST

Babel example implementing the visitor pattern

export default function () {
  return {
    visitor: {
      VariableDeclaration(path) {
        console.log({ VariableDeclaration: path.node });
        // { type: "VariableDeclaration", kind: "var", ... }
      },
      Identifier(path) {
        console.log({ Identifier: path.node });
        // { type: "Identifier", name: "foo", ... }
      },
      NumericLiteral(path) {
        console.log({ NumericLiteral: path.node });
        // { type: "NumericLiteral", value: 10, ... }
      }
    }
  };
}

@MicheleRivaCode

Transforming

Babel example

export default function() {  
  return {
    visitor: {
      VariableDeclaration(path) {
        if (path.node.kind === "var") {
          path.node.kind = "let"
        }
      }
    }
  };
}

@MicheleRivaCode

Codegen

Babel example

var foo = 10
const bar = true
let foo = 10;
const bar = true;

input

output

@MicheleRivaCode

ESLint

jscodeshift

Prettier

@MicheleRivaCode

ESLint

export default function(context) {
  return {
    TemplateLiteral(node) {
      context.report({
        node,
        message: 'Do not use template literals',

        fix(fixer) {
          if (node.expressions.length) {
            // Can't auto-fix template literal with expressions
            return;
          }
          
          return [
            fixer.replaceTextRange([node.start, node.start + 1], '"'),
            fixer.replaceTextRange([node.end - 1, node.end], '"'),
          ];
        },
      });
    }
  };
};

Example code taken from astexplorer.net

@MicheleRivaCode

ESLint

const myString = `Hello, World!`;
const myString = "Hello, World!";

input

output

@MicheleRivaCode

export const parser = "babel";

export default function transformer(file, api) {
  const j = api.jscodeshift;

  return j(file.source)
    .find(j.Identifier)
    .forEach((path) => {
      if (j(path).get().value.name.includes("oldName")) {
        j(path).replaceWith(
          j.identifier(path.node.name.replace("oldName", "newName"))
        );
      }
    })
    .toSource();
}
jscodeshift

@MicheleRivaCode

jscodeshift
const oldNameFactory = () => {/* */};
const newNameFactory = () => {/* */};

input

output

@MicheleRivaCode

ttypescript (https://github.com/cevek/ttypescript)
export default function (program) {
  const checker = program.getTypeChecker();
  return (context) => {
    return (sourceFile) => {
      const visitor = (node) => {
        // This branch evaluates '2 + 2' like expressions and replaces the node with the result (in this case '4')
        if (ts.isBinaryExpression(node)) {
          if (ts.isNumericLiteral(node.left) && ts.isNumericLiteral(node.right)) {
            // We could parse `node.text` as a number, or we can use the typechecker to get type info for nodes
            const lhs = checker.getTypeAtLocation(node.left);
            const rhs = checker.getTypeAtLocation(node.right);

            switch (node.operatorToken.kind) {
              case ts.SyntaxKind.PlusToken:
                return context.factory.createNumericLiteral(lhs.value + rhs.value);
            }
          }
        }
        // 
        if (ts.isIdentifier(node) && node.text === 'printTips' || node.text === 'tips') {
            return context.factory.createIdentifier(node.text.split('').reverse().join(''));
        }
        return ts.visitEachChild(node, visitor, context);
      };
      return ts.visitNode(sourceFile, visitor);
    };
  };
};

Example code taken from https://github.com/cevek/ttypescript#program

@MicheleRivaCode

ttypescript (https://github.com/cevek/ttypescript)
const mySum = 10 + 20;
const mySum = 30;

input

output

@MicheleRivaCode

Babel

TSLint

ESLint

Recast

Prettier

JSCodeshift

@MicheleRivaCode

@MicheleRivaCode

@MicheleRivaCode

@MicheleRiva

@MicheleRivaCode

/in/MicheleRiva95

www.micheleriva.it

Understanding JavaScript Compilation

By Michele Riva

Understanding JavaScript Compilation

  • 284