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
- 368