Applied Enterprise
Metaprogramming
in JavaScript
Vladyslav Dukhin
Lead Software Engineer at SoftServe
Metaprogramming

Theory
Node.js VM
Practical use cases
Metaprogramming

Theory
Node.js VM
Practical use cases
metaprogramming
metamemory
metaphor
metamorphism
metadata
metarule
metatheory
metalanguage
metamathematics
metaprogramming
metamemory
metaphor
metamorphism
metadata
metarule
metatheory
metalanguage
metamathematics
metaprogramming
metamemory
metaphor
metamorphism
metadata
metarule
metatheory
metalanguage
metamathematics
abstraction
A mechanism of hiding complicated
object details for the sake of simplicity
and ease of manipulation
abstraction layer
A way of hiding the details of a system
leading to interoperability, compatibility
and platform independence of this
system
OSI model
Physical Data Link Netwok Transport Session Presentation Application
Butler Lampson & Kevlin Henney
"All problems in computer science can be
solved by another level of abstraction, except
for the problem of too many layers of
abstraction"
Butler Lampson & Kevlin Henney
"All problems in computer science can be
solved by another level of abstraction, except
for the problem of too many layers of
abstraction"
Butler Lampson & Kevlin Henney
"All problems in computer science can be
solved by another level of indirection, except
for the problem of too many layers of
indirection"
What is the difference between
abstraction and indirection layers?
metaprogramming
Writing a program that can inspect or
modify the behaviour of itself or other
programs
metaprogramming
Treating source code as data that the
program can write, modify or inspect
Metaprogramming
Metaprogramming
Code Generation Code Analysis Code Transformation Self-modification
Metaprogramming
Code Generation Code Analysis Code Transformation Self-modification
Code generation
Runtime Design time Compile time
Emmet
https://emmet.io
https://code.visualstudio.com/docs/editor/emmet
https://www.jetbrains.com/help/webstorm/settings-emmet.html
Code Snippets
Snippets for VS Code: Jest Snippets, JavaScript code snippets
https://code.visualstudio.com/docs/editor/userdefinedsnippets
https://www.jetbrains.com/webstorm/guide/tips/save-as-live-template
{
"React funtional component template": {
"description": "React funtional component template",
"scope": "javascript,typescript",
"prefix": "fc",
"body": [
"import React from 'react';",
"import PropTypes from 'prop-types';n",
"export function ${1:ComponentName}({ ${2:props} }) {",
"treturn (",
"tt$0",
"t);",
"}n",
"${1:ComponentName}.propTypes = {",
"t${2/([^,s]+),?/$1: PropTypes.any,/g}",
"};"
]
}
}
Own IDE snippets
scaffolding
A technique related to either a project
generation or code generation for data
access
Yeoman Generator: https://yeoman.io
npm install yo generator-code && yo code
Create React App: https://create-react-app.dev
npx create-react-app my-react-app
Loopback: https://loopback.io/doc/en/lb4/Getting-started.html
npm install @loopback/cli && npx lb4 app
Angular: https://github.com/angular/angular-cli
npm install @angular/cli && npx ng new angular-project-name
Code generation
Runtime Design time Compile time
transpiling
A translation of the source code from
one programming language to another
How is transpiling different
from the compilation?
How do transpilation and compilation work?

https://github.com/jamiebuilds/the-super-tiny-compiler

https://astexplorer.net
Babel plugin Yeoman generator

https://github.com/babel/generator-babel-plugin
How to create a Babel plugin?

https://github.com/jamiebuilds/babel-handbook
A collection of JavaScript code modifiers

https://github.com/cpojer/js-codemod
A collection of React.js code modifiers

https://github.com/reactjs/react-codemod
jscodeshift

https://github.com/facebook/jscodeshift
Code modifiers to migrate to Jest framework

https://github.com/skovhus/jest-codemods
npx react-codemod pure-component <path>

npx react-codemod update-react-imports <path>
Code generation
Runtime Design time Compile time
const json = '{"a":1,"b":2,"c":true,"d":"string","e":null}';
const evalJSON = eval('(' + json + ')');
const staticCodeExecutionResult = eval(`
const a = 7;
const b = 243;
a * b;
`);
const arr = [5, 7, 2, 3, 1, 4, 0];
const dynamicCodeExecutionResult = eval(`
[Math.min(${arr}), Math.max(${arr})]
`);
console.log(evalJSON);
console.log(staticCodeExecutionResult);
console.log(dynamicCodeExecutionResult);
Eval
const json = '{"a":1,"b":2,"c":true,"d":"string","e":null}';
const parseJSON = new Function('json', 'return JSON.parse(json)');
const parseJSONResult = parseJSON(json);
const multiply = new Function('a', 'b', 'return a * b');
const multiplyResult = multiply(7, 243);
const arr = [5, 7, 2, 3, 1, 4, 0];
const minMax = new Function('arr', 'return [Math.min(...arr), Math.max(...arr)]');
const minMaxResult = minMax(arr);
console.log(parseJSONResult);
console.log(multiplyResult);
console.log(minMaxResult);
new Function
import vm from 'vm';
global.arr = [5, 7, 2, 3, 1, 4, 0];
export const script = new vm.Script(`
[Math.min(...arr), Math.max(...arr)]
`);
const scriptMinMaxResult = script.runInThisContext();
const minMaxFunction = vm.compileFunction(`
return [Math.min(...arr), Math.max(...arr)]
`, ['arr']);
const functionMinMaxResult = minMaxFunction(global.arr);
const contextMinMaxResult = vm.runInThisContext(`
[Math.min(...arr), Math.max(...arr)]
`);
Node.js V8 Virtual Machine
--disallow-code-generation-from-strings
From the Node.js Docs: Make built-in language features like eval and new Function that generate code from strings throw an exception
instead. This does not affect the Node.js vm module. Added in: v9.8.0.
Performance comparison
general function x 15,566,583 ops/sec ±0.59% (84 runs sampled)

VM compileFunction x 15,483,118 ops/sec ±0.83% (86 runs sampled)
VM runInThisContext x 13,527 ops/sec ±50.77% (29 runs sampled)
VM script.runInThisContext x 864,776 ops/sec ±4.48% (85 runs sampled)
eval x 4,722,036 ops/sec ±0.67% (89 runs sampled)
new Function x 15,626,702 ops/sec ±0.78% (83 runs sampled)
Fastest is new Function, VM compileFunction and general function
0
4,000,000
8,000,000
12,000,000
16,000,000
compileFunction runInThisCtx script.runInThisCtx eval new Function general
15,566,583
15,626,702
4,722,036
864,776
13,527
15,483,118
ops/sec
Metaprogramming
Code Generation Code Analysis Code Transformation Self-modification
Metaprogramming
Code Generation Code Analysis Code Transformation Self-modification
introspection
A process of examining types, state and
properties of a code in the runtime
interception
A process of intercepting and redefining
fundamental language operations on an
object
reflection
The ability of a program to modify itself
at runtime by making use of
introspection and interception
Introspection in JavaScript
const symbol = Symbol('for object');
const objToIntrospect = {
keyOne: 1,
keyTwo: 10n,
keyThree: true,
keyFour: 'string',
[symbol]: null,
};
objToIntrospect.constructor; // => link to [Function: Object]
(100).constructor; // => link to [Function: Number]
(true).constructor; // => link to [Function: Boolean]
(() => {}).constructor; // => link to [Function: Function]
Introspection in JavaScript
objToIntrospect.prototype; // => undefined
Object.getPrototypeOf(objToIntrospect);
// => [Object: null prototype] {}
Reflect.getPrototypeOf(objToIntrospect);
// => [Object: null prototype] {}
Object.keys(objToIntrospect);
// => [ 'keyOne', 'keyTwo', 'keyThree', 'keyFour' ]
Object.getOwnPropertyNames(objToIntrospect);
// => [ 'keyOne', 'keyTwo', 'keyThree', 'keyFour' ]
Object.getOwnPropertySymbols(objToIntrospect);
// => [ Symbol(for object) ]
Reflect.ownKeys(objToIntrospect);
// => [ 'keyOne', 'keyTwo', 'keyThree', 'keyFour', Symbol(for object) ]
Introspection in JavaScript
Object.values(objToIntrospect);
// => [ 1, 10n, true, 'string' ]
Object.entries(objToIntrospect);
// => [['keyOne', 1], ['keyTwo', 10n], ['keyThree', true], ['keyFour', 'string']]
Object.prototype.hasOwnProperty.call(objToIntrospect, 'keyOne');
// => true
Object.hasOwn(objToIntrospect, 'keyOne'); // => true
Reflect.has(objToIntrospect, 'keyOne'); // => true
Introspection in JavaScript
Object.isExtensible(objToIntrospect); // => true
Reflect.isExtensible(objToIntrospect); // => true
Object.isFrozen(objToIntrospect); // => false
Object.isSealed(objToIntrospect); // => false
Object.getOwnPropertyDescriptor(objToIntrospect, symbol);
// => { value: null, writable: true, enumerable: true, configurable: true }
Reflect.getOwnPropertyDescriptor(objToIntrospect, symbol);
// => { value: null, writable: true, enumerable: true, configurable: true }
Object.getOwnPropertyDescriptors(objToIntrospect);
Introspection in JavaScript
const functionToIntrospect = (a, b, c = 1) => {
// returns a sum of arguments
return a + b + c;
};
functionToIntrospect.length; // => 2 (one argument is optional)
functionToIntrospect.name; // => functionToIntrospect
functionToIntrospect.toString();
/* =>
(a, b, c = 1) => {
// returns a sum of arguments
return a + b + c;
}
*/
function parseCustomHookName(functionName: void | string): string {
if (!functionName) {
return '';
}
let startIndex = functionName.lastIndexOf('.');
if (startIndex === -1) {
startIndex = 0;
}
if (functionName.substr(startIndex, 3) === 'use') {
startIndex += 3;
}
return functionName.substr(startIndex);
}
function isHookName(s) {
return /^use[A-Z0-9].*$/.test(s);
}
Source: https://github.com/facebook/react
From React 17.0.2 documentation:
A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next);
} catch (err) {
next(err);
}
};
Source: https://github.com/expressjs/express
Error handling middleware in Express.js
Find out more at http://expressjs.com/en/guide/error-handling.html
Introspection in JavaScript
class ClassToIntrospect {
// constructor stores provided arguments
constructor(a, b, c = 1) {
this.a = a; this.b = b; this.c = c;
}
}
ClassToIntrospect.name; // => ClassToIntrospect
ClassToIntrospect.length;
// => 2 (constructor length, one argument is optional)
ClassToIntrospect.toString();
/* =>
class ClassToIntrospect {
// constructor stores provided arguments
constructor(a, b, c = 1) {
this.a = a;
this.b = b;
this.c = c;
}
}
*/
typeof variable;
variable instanceof Object;
Array.isArray(variable);
ArrayBuffer.isView(variable);
Should we treat RTTI
as introspection?
Introspection in Node.js
CommonJS Modules
__dirname; // => /path/to/the
__filename; // => /path/to/the/nodejs.js
require.main; // metadata of the entrypoint module
require.main === module; // is an entrypoint module
module.filename; // => /path/to/the/nodejs.js
module.path; // => /path/to/the
ECMAScript Modules
import.meta;
// => [Object: null prototype] {
// url: 'file:///path/to/the/nodejs.mjs'
// }
Introspection in V8
const exampleFunction = (...args) => Math.max(...args);
%FunctionGetScriptSourcePosition(exampleFunction); // => 97
%GetOptimizationStatus(exampleFunction);
Find more information at https://github.com/v8/v8/blob/master/src/runtime/runtime.h
Quine program
s="s=%j;console.log(s,s)";console.log(s,s)
console.log(%FunctionGetScriptSource(() => {}));
Let’s introspect
the JavaScript array
const arr = [1, 2, 3];
arr.toString(); // => '1, 2, 3'
Reflect.ownKeys(arr); // => ['0', '1', '2', 'length']
typeof arr; // => 'object'
Is array an object?
Yes!
Reflect.ownKeys(Array.prototype);
/* =>
[
'length',
'constructor',
'concat',
'copyWithin',
'fill',
'find',
'findIndex',
'lastIndexOf',
'pop',
'push',
'reverse',
'shift',
'unshift',
…
Symbol(Symbol.iterator),
Symbol(Symbol.unscopables)
]
*/
arr[Symbol.iterator].toString();
// => function values() { [native code] }
arr[Symbol.unscopables];
/* =>
[Object: null prototype] {
copyWithin: true,
entries: true,
fill: true,
find: true,
findIndex: true,
flat: true,
flatMap: true,
includes: true,
keys: true,
values: true,
at: true
}
*/
{
__type(name: "Character") {
name
kind
}
}
{
"data": {
"__type": {
"name": "Character",
"kind": "INTERFACE"
}
}
}
QraphQL Introspection
Find more at https://graphql.org/learn/introspection/
Symbol.iterator – used by for…of loop
Symbol.asyncIterator – used by for await...of loop
Symbol.hasInstance – used by instanceof operator
Symbol.isConcatSpreadable – used by Array.prototype.concat() method
Symbol.match – used by String.prototype.match() method
Symbol.matchAll – used by String.prototype.matchAll() method
Reflection in JavaScript
Reflection in JavaScript
Symbol.replace – used by String.prototype.replace() method
Symbol.search – used by String.prototype.search() method
Symbol.split – used by String.prototype.split() method
Symbol.toStringTag – used by Object.prototype.toString() method
Symbol.unscopables – specifies an object value containing keys that are
excluded from the "with" statement environment bindings
Symbol.toPrimitive – specifies a function valued property that is called to
convert an object to a corresponding primitive value
Symbol.species – specifies a function-valued property that the constructor
function uses to create derived objects
const range = (start, end, step = 1) => ({
*[Symbol.iterator]() {
for (let i = start; i <= end; i += step) {
yield i;
}
}
});
for (const n of range(10, 20)) {
console.log(n); // => 10...20
}
class User {
constructor(name, age, password) {
this.name = name;
this.age = age;
this.password = password;
}
[Symbol.toPrimitive](hint) {
if (hint === 'number') {
return this.age;
}
if (hint === 'string') {
return `${this.name} (${this.age} years old)`;
}
return this;
}
}
const user = new User('Charles Gibson', 54, 'SeCreT-PassW0rd');

console.log(+user); // => 54
console.log(`${user}`); // => Charles Gibson (54 years old)

console.log(user);
// => User { name: 'Charles Gibson', age: 54, password: 'SeCreT-PassW0rd' }
const inspect = Symbol.for('nodejs.util.inspect.custom');
class User {
static [Symbol.hasInstance](instance) {
if (instance === this) return true;
if (instance.password === '*****') return true;
return false;
}
[inspect]() {
const userFakePassword = {
...this,
password: '*****',
};
Object.defineProperty(userFakePassword, 'constructor', {
value: User,
enumerable: false,
});
return userFakePassword;
}
}
const user = new User('Charles Gibson', 54, 'SeCreT-PassW0rd');
console.log(user);
// => { name: 'Charles Gibson', age: 54, password: '*****' }
Reflection in JavaScript
const exampleObject = { a: 1, b: 2 };
Object.assign({ a: 0 }, { b: 1 }); // => { a: 0, b: 1 }
Object.assign({}, 'abc'); // => { '0': 'a', '1': 'b', '2': 'c' }
Object.create({});
Object.create(null);
Object.fromEntries([['a', 1], ['b', 2]]); // => { a: 1, b: 2 }
Object.setPrototypeOf(exampleObject, {});
Reflect.setPrototypeOf(exampleObject, {});
Object.defineProperty(exampleObject, 'c', { value: 4 });
Reflect.defineProperty(exampleObject, 'c', { value: 4 });
Object.defineProperties(exampleObject, { d: { value: 5 } });
Reflection in JavaScript
class Example {
a = 1;
b = 2;
}
const example = new Example();
example; // => Example { a: 1, b: 2 }
example.constructor; // => [class Example]
example instanceof Example; // => true
Object.assign(example, { c: 10 });
example; // => Example { a: 1, b: 2, c: 10 }
example.constructor; // => [class Example]
example instanceof Example; // => true
Reflection in JavaScript
Object.freeze(exampleObject);
Object.preventExtensions(exampleObject);
Reflect.preventExtensions(exampleObject);
Object.seal(exampleObject);
Reflect.construct(Set, [[1, 2, 3]]); // = new Set([1, 2, 3])
Reflect.deleteProperty(exampleObject, 'a'); // = delete exampleObject.a;
Reflect.get(exampleObject, 'b'); // = exampleObject.b
Reflect.set(exampleObject, 'a', 1); // equals exampleObject.a = 1
Interception in JavaScript
new Proxy(target, handler)
handler.apply()
handler.construct()
handler.defineProperty()
handler.deleteProperty()
handler.get()
handler.getOwnPropertyDescriptor()
handler.getPrototypeOf()
handler.has()
handler.isExtensible()
handler.ownKeys()
handler.preventExtensions()
handler.set()
handler.setPrototypeOf()
Performance comparison
obj.get x 931,245,919 ops/sec ±1.49% (85 runs sampled)
Proxy.get x 23,572,175 ops/sec ±0.64% (87 runs sampled)
obj.get is faster by 40x
0
250,000,000
500,000,000
750,000,000
1,000,000,000
obj.get Proxy.get
23,572,175
931,245,919
ops/sec
Interception in JavaScript
console.log(15 in range(10, 20));
for (const n of range(10, 20)) {
console.log(n);
}
Interception in JavaScript
const iterator = {
*[Symbol.iterator]() {
for (let i = start; i <= end; i += step) {
yield i;
}
}
};
const handlers = {
has(target, key) {
if (typeof key === 'symbol') {
return Reflect.has(target, key);
}
let n = Number(key); // cast type to number
if (n >= start && n <= end) {
return true;
}
return false;
}
};
const range = (start, end, step = 1) => {
return new Proxy(iterator, handlers);
};
Node.js Module Loaders
import css from './sample.css';
console.dir(css);
.hello {
content: 'Hello, World!';
color: cornflowerblue;
background-color: rgba(25, 25, 125, 1);
text-align: center;
}
.wow {
animation: linear 0.1s linear;
}
import { URL, pathToFileURL } from 'url';
import { cwd } from 'process';
import cssToJS from 'transform-css-to-js';
const baseURL = pathToFileURL(`${cwd()}/`).href;
export function resolve(specifier, context, defaultResolve) {
const { parentURL = baseURL } = context;
if (specifier.endsWith('.css')) {
return { url: new URL(specifier, parentURL).href };
}
return defaultResolve(specifier, context, defaultResolve);
}
export async function load(url, context, defaultLoad) {
if (url.endsWith('.css')) {
const { source: rawSource } = await defaultLoad(url, { format: 'module' });
return {
format: 'module',
source: 'export default ' + cssToJS(rawSource.toString(), true),
};
}
return defaultLoad(url, context, defaultLoad);
}
function addNumbers(a: number, b: number) {
return a + b;
}
var sum = addNumbers(10, 15)
console.log('Sum of the two numbers is: ' + sum);
node --experimental-loader=ts-node/esm example.ts
Metaprogramming examples
function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}
Mocks and stubs during testing
// test
jest.useFakeTimers();
jest.spyOn(global, 'setTimeout');
test('waits 1 second before ending the game', () => {
const timerGame = require('../timerGame');
timerGame();
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
});
import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'process';
import express from 'express';
const app = express();
const cli = readline.createInterface({ input, output });
function displayCliMessage(...messages) {
output.clearLine(-1);
output.cursorTo(0);
console.log(...messages);
cli.prompt();
}
app.locals.featureFlags = {
shouldDisplayGreeting: true,
};
app.get('/', (req, res, next) => {
const { shouldDisplayGreeting } = res.app.locals.featureFlags;
displayCliMessage('shouldDisplayGreeting', shouldDisplayGreeting);
res.setHeader('Content-Type', 'text/plain');
if (shouldDisplayGreeting) {
const userName = req.query?.name ?? 'Anonymous';
return res.end(`Greetings, ${userName}! It's hello from server!`);
}
return res.end('Hello from server!');
});
app.listen(3000, () => {
displayCliMessage('Server is listening on port 3000');
});
cli.on('line', (command) => {
if (command === 'toggle') {
const { shouldDisplayGreeting } = app.locals.featureFlags;
app.locals.featureFlags.shouldDisplayGreeting = !shouldDisplayGreeting;
displayCliMessage('"shouldDisplayGreeting" flag is toggled!');
}
});
Feature flags
Function arguments validation
const validatedFunction = (userName /* string */, userAge /* number */, callback /* function */) => {
// here we are sure that arguments are of a valid type
};
Function arguments validation
const validatedFunction = validate((userName /* string */, userAge /* number */, callback /* function */) => {
// here we are sure that arguments are of a valid type
});
const validate = (fn) => {
const functionSourceCode = fn.toString();
const tokens = tokenize(functionSourceCode);
const annotations = tokens.filter(token => token.type === 'annotation');
return (...args) => {
if (args.some((arg, i) => typeof arg !== annotations[i].token)) {
throw new Error('invalid arguments');
}
return fn(...args);
};
}
const tokenize = (functionSourceCode) => {
const buffer = Buffer.from(functionSourceCode);
const bufferLength = buffer.length;
let parserMode = PARSER_MODES.ARGUMENT;
let handler = PARSER_MODE_HANDLERS[parserMode];
const ctx = {
charCode: 0,
tokens: [],
index: buffer.indexOf(40) + 1, // '('
isToken: false,
tokenStart: 0,
buffer,
};
let parenthesisBalanceCount = ctx.index > 0 ? 1 : 0;
while (parenthesisBalanceCount > 0 && ctx.index < bufferLength) {
ctx.charCode = ctx.buffer[ctx.index];
handler = PARSER_MODE_HANDLERS[parserMode];
parserMode = handler(ctx);
if (ctx.charCode === 40 /* ( */) {
++parenthesisBalanceCount;
} else if (ctx.charCode === 41 /* ) */ && parserMode === PARSER_MODES.ARGUMENT) {
--parenthesisBalanceCount;
}
if (parenthesisBalanceCount === 0) break;
++ctx.index;
}
return ctx.tokens;
};
PARSER_MODE_HANDLERS[PARSER_MODES.ARGUMENT] = (ctx) => {
if (ctx.charCode === 47) { // '/'
const nextCharCode = ctx.buffer[ctx.index + 1];
if (nextCharCode === 42) { // '*'
return PARSER_MODES.MULTILINE_ANNOTATION;
}
if (nextCharCode === 47) { // '/'
return PARSER_MODES.LINE_ANNOTATION;
}
}
if (!ctx.isToken && isAlphaNumeric(ctx.charCode)) {
ctx.tokenStart = ctx.index;
ctx.isToken = true;
} else if (ctx.isToken && !isAlphaNumeric(ctx.charCode)) {
const token = ctx.buffer.toString(undefined, ctx.tokenStart, ctx.index);
ctx.tokens.push({ type: 'argument', token });
ctx.isToken = false;
}
return PARSER_MODES.ARGUMENT;
};
Find out more on my GitHub
github.com/primeare
linkedin.com/in/vladyslav-dukhin
instagram.com/vladyslav.dev
Thank you for
your time

"Applied Enterprise Metaprogramming in JavaScript", Vladyslav Dukhin

  • 1.
    Applied Enterprise Metaprogramming in JavaScript VladyslavDukhin Lead Software Engineer at SoftServe
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
    abstraction A mechanism ofhiding complicated object details for the sake of simplicity and ease of manipulation
  • 8.
    abstraction layer A wayof hiding the details of a system leading to interoperability, compatibility and platform independence of this system
  • 9.
    OSI model Physical DataLink Netwok Transport Session Presentation Application
  • 10.
    Butler Lampson &Kevlin Henney "All problems in computer science can be solved by another level of abstraction, except for the problem of too many layers of abstraction"
  • 11.
    Butler Lampson &Kevlin Henney "All problems in computer science can be solved by another level of abstraction, except for the problem of too many layers of abstraction"
  • 12.
    Butler Lampson &Kevlin Henney "All problems in computer science can be solved by another level of indirection, except for the problem of too many layers of indirection"
  • 13.
    What is thedifference between abstraction and indirection layers?
  • 14.
    metaprogramming Writing a programthat can inspect or modify the behaviour of itself or other programs
  • 15.
    metaprogramming Treating source codeas data that the program can write, modify or inspect
  • 16.
  • 17.
    Metaprogramming Code Generation CodeAnalysis Code Transformation Self-modification
  • 18.
    Metaprogramming Code Generation CodeAnalysis Code Transformation Self-modification
  • 19.
  • 21.
  • 23.
    Code Snippets Snippets forVS Code: Jest Snippets, JavaScript code snippets https://code.visualstudio.com/docs/editor/userdefinedsnippets https://www.jetbrains.com/webstorm/guide/tips/save-as-live-template
  • 24.
    { "React funtional componenttemplate": { "description": "React funtional component template", "scope": "javascript,typescript", "prefix": "fc", "body": [ "import React from 'react';", "import PropTypes from 'prop-types';n", "export function ${1:ComponentName}({ ${2:props} }) {", "treturn (", "tt$0", "t);", "}n", "${1:ComponentName}.propTypes = {", "t${2/([^,s]+),?/$1: PropTypes.any,/g}", "};" ] } } Own IDE snippets
  • 26.
    scaffolding A technique relatedto either a project generation or code generation for data access
  • 27.
    Yeoman Generator: https://yeoman.io npminstall yo generator-code && yo code Create React App: https://create-react-app.dev npx create-react-app my-react-app Loopback: https://loopback.io/doc/en/lb4/Getting-started.html npm install @loopback/cli && npx lb4 app Angular: https://github.com/angular/angular-cli npm install @angular/cli && npx ng new angular-project-name
  • 28.
  • 29.
    transpiling A translation ofthe source code from one programming language to another
  • 30.
    How is transpilingdifferent from the compilation?
  • 31.
    How do transpilationand compilation work?
 https://github.com/jamiebuilds/the-super-tiny-compiler
 https://astexplorer.net Babel plugin Yeoman generator
 https://github.com/babel/generator-babel-plugin How to create a Babel plugin?
 https://github.com/jamiebuilds/babel-handbook
  • 32.
    A collection ofJavaScript code modifiers
 https://github.com/cpojer/js-codemod A collection of React.js code modifiers
 https://github.com/reactjs/react-codemod jscodeshift
 https://github.com/facebook/jscodeshift Code modifiers to migrate to Jest framework
 https://github.com/skovhus/jest-codemods npx react-codemod pure-component <path>
 npx react-codemod update-react-imports <path>
  • 33.
  • 34.
    const json ='{"a":1,"b":2,"c":true,"d":"string","e":null}'; const evalJSON = eval('(' + json + ')'); const staticCodeExecutionResult = eval(` const a = 7; const b = 243; a * b; `); const arr = [5, 7, 2, 3, 1, 4, 0]; const dynamicCodeExecutionResult = eval(` [Math.min(${arr}), Math.max(${arr})] `); console.log(evalJSON); console.log(staticCodeExecutionResult); console.log(dynamicCodeExecutionResult); Eval
  • 35.
    const json ='{"a":1,"b":2,"c":true,"d":"string","e":null}'; const parseJSON = new Function('json', 'return JSON.parse(json)'); const parseJSONResult = parseJSON(json); const multiply = new Function('a', 'b', 'return a * b'); const multiplyResult = multiply(7, 243); const arr = [5, 7, 2, 3, 1, 4, 0]; const minMax = new Function('arr', 'return [Math.min(...arr), Math.max(...arr)]'); const minMaxResult = minMax(arr); console.log(parseJSONResult); console.log(multiplyResult); console.log(minMaxResult); new Function
  • 36.
    import vm from'vm'; global.arr = [5, 7, 2, 3, 1, 4, 0]; export const script = new vm.Script(` [Math.min(...arr), Math.max(...arr)] `); const scriptMinMaxResult = script.runInThisContext(); const minMaxFunction = vm.compileFunction(` return [Math.min(...arr), Math.max(...arr)] `, ['arr']); const functionMinMaxResult = minMaxFunction(global.arr); const contextMinMaxResult = vm.runInThisContext(` [Math.min(...arr), Math.max(...arr)] `); Node.js V8 Virtual Machine
  • 37.
    --disallow-code-generation-from-strings From the Node.jsDocs: Make built-in language features like eval and new Function that generate code from strings throw an exception instead. This does not affect the Node.js vm module. Added in: v9.8.0.
  • 38.
    Performance comparison general functionx 15,566,583 ops/sec ±0.59% (84 runs sampled)
 VM compileFunction x 15,483,118 ops/sec ±0.83% (86 runs sampled) VM runInThisContext x 13,527 ops/sec ±50.77% (29 runs sampled) VM script.runInThisContext x 864,776 ops/sec ±4.48% (85 runs sampled) eval x 4,722,036 ops/sec ±0.67% (89 runs sampled) new Function x 15,626,702 ops/sec ±0.78% (83 runs sampled) Fastest is new Function, VM compileFunction and general function 0 4,000,000 8,000,000 12,000,000 16,000,000 compileFunction runInThisCtx script.runInThisCtx eval new Function general 15,566,583 15,626,702 4,722,036 864,776 13,527 15,483,118 ops/sec
  • 39.
    Metaprogramming Code Generation CodeAnalysis Code Transformation Self-modification
  • 40.
    Metaprogramming Code Generation CodeAnalysis Code Transformation Self-modification
  • 41.
    introspection A process ofexamining types, state and properties of a code in the runtime
  • 42.
    interception A process ofintercepting and redefining fundamental language operations on an object
  • 43.
    reflection The ability ofa program to modify itself at runtime by making use of introspection and interception
  • 44.
    Introspection in JavaScript constsymbol = Symbol('for object'); const objToIntrospect = { keyOne: 1, keyTwo: 10n, keyThree: true, keyFour: 'string', [symbol]: null, }; objToIntrospect.constructor; // => link to [Function: Object] (100).constructor; // => link to [Function: Number] (true).constructor; // => link to [Function: Boolean] (() => {}).constructor; // => link to [Function: Function]
  • 45.
    Introspection in JavaScript objToIntrospect.prototype;// => undefined Object.getPrototypeOf(objToIntrospect); // => [Object: null prototype] {} Reflect.getPrototypeOf(objToIntrospect); // => [Object: null prototype] {} Object.keys(objToIntrospect); // => [ 'keyOne', 'keyTwo', 'keyThree', 'keyFour' ] Object.getOwnPropertyNames(objToIntrospect); // => [ 'keyOne', 'keyTwo', 'keyThree', 'keyFour' ] Object.getOwnPropertySymbols(objToIntrospect); // => [ Symbol(for object) ] Reflect.ownKeys(objToIntrospect); // => [ 'keyOne', 'keyTwo', 'keyThree', 'keyFour', Symbol(for object) ]
  • 46.
    Introspection in JavaScript Object.values(objToIntrospect); //=> [ 1, 10n, true, 'string' ] Object.entries(objToIntrospect); // => [['keyOne', 1], ['keyTwo', 10n], ['keyThree', true], ['keyFour', 'string']] Object.prototype.hasOwnProperty.call(objToIntrospect, 'keyOne'); // => true Object.hasOwn(objToIntrospect, 'keyOne'); // => true Reflect.has(objToIntrospect, 'keyOne'); // => true
  • 47.
    Introspection in JavaScript Object.isExtensible(objToIntrospect);// => true Reflect.isExtensible(objToIntrospect); // => true Object.isFrozen(objToIntrospect); // => false Object.isSealed(objToIntrospect); // => false Object.getOwnPropertyDescriptor(objToIntrospect, symbol); // => { value: null, writable: true, enumerable: true, configurable: true } Reflect.getOwnPropertyDescriptor(objToIntrospect, symbol); // => { value: null, writable: true, enumerable: true, configurable: true } Object.getOwnPropertyDescriptors(objToIntrospect);
  • 48.
    Introspection in JavaScript constfunctionToIntrospect = (a, b, c = 1) => { // returns a sum of arguments return a + b + c; }; functionToIntrospect.length; // => 2 (one argument is optional) functionToIntrospect.name; // => functionToIntrospect functionToIntrospect.toString(); /* => (a, b, c = 1) => { // returns a sum of arguments return a + b + c; } */
  • 49.
    function parseCustomHookName(functionName: void| string): string { if (!functionName) { return ''; } let startIndex = functionName.lastIndexOf('.'); if (startIndex === -1) { startIndex = 0; } if (functionName.substr(startIndex, 3) === 'use') { startIndex += 3; } return functionName.substr(startIndex); } function isHookName(s) { return /^use[A-Z0-9].*$/.test(s); } Source: https://github.com/facebook/react From React 17.0.2 documentation: A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.
  • 50.
    Layer.prototype.handle_request = functionhandle(req, res, next) { var fn = this.handle; if (fn.length > 3) { // not a standard request handler return next(); } try { fn(req, res, next); } catch (err) { next(err); } }; Source: https://github.com/expressjs/express Error handling middleware in Express.js Find out more at http://expressjs.com/en/guide/error-handling.html
  • 51.
    Introspection in JavaScript classClassToIntrospect { // constructor stores provided arguments constructor(a, b, c = 1) { this.a = a; this.b = b; this.c = c; } } ClassToIntrospect.name; // => ClassToIntrospect ClassToIntrospect.length; // => 2 (constructor length, one argument is optional) ClassToIntrospect.toString(); /* => class ClassToIntrospect { // constructor stores provided arguments constructor(a, b, c = 1) { this.a = a; this.b = b; this.c = c; } } */
  • 52.
    typeof variable; variable instanceofObject; Array.isArray(variable); ArrayBuffer.isView(variable); Should we treat RTTI as introspection?
  • 53.
    Introspection in Node.js CommonJSModules __dirname; // => /path/to/the __filename; // => /path/to/the/nodejs.js require.main; // metadata of the entrypoint module require.main === module; // is an entrypoint module module.filename; // => /path/to/the/nodejs.js module.path; // => /path/to/the ECMAScript Modules import.meta; // => [Object: null prototype] { // url: 'file:///path/to/the/nodejs.mjs' // }
  • 54.
    Introspection in V8 constexampleFunction = (...args) => Math.max(...args); %FunctionGetScriptSourcePosition(exampleFunction); // => 97 %GetOptimizationStatus(exampleFunction); Find more information at https://github.com/v8/v8/blob/master/src/runtime/runtime.h
  • 55.
  • 56.
  • 57.
    const arr =[1, 2, 3]; arr.toString(); // => '1, 2, 3' Reflect.ownKeys(arr); // => ['0', '1', '2', 'length'] typeof arr; // => 'object' Is array an object? Yes!
  • 58.
    Reflect.ownKeys(Array.prototype); /* => [ 'length', 'constructor', 'concat', 'copyWithin', 'fill', 'find', 'findIndex', 'lastIndexOf', 'pop', 'push', 'reverse', 'shift', 'unshift', … Symbol(Symbol.iterator), Symbol(Symbol.unscopables) ] */ arr[Symbol.iterator].toString(); // =>function values() { [native code] } arr[Symbol.unscopables]; /* => [Object: null prototype] { copyWithin: true, entries: true, fill: true, find: true, findIndex: true, flat: true, flatMap: true, includes: true, keys: true, values: true, at: true } */
  • 59.
    { __type(name: "Character") { name kind } } { "data":{ "__type": { "name": "Character", "kind": "INTERFACE" } } } QraphQL Introspection Find more at https://graphql.org/learn/introspection/
  • 60.
    Symbol.iterator – usedby for…of loop Symbol.asyncIterator – used by for await...of loop Symbol.hasInstance – used by instanceof operator Symbol.isConcatSpreadable – used by Array.prototype.concat() method Symbol.match – used by String.prototype.match() method Symbol.matchAll – used by String.prototype.matchAll() method Reflection in JavaScript
  • 61.
    Reflection in JavaScript Symbol.replace– used by String.prototype.replace() method Symbol.search – used by String.prototype.search() method Symbol.split – used by String.prototype.split() method Symbol.toStringTag – used by Object.prototype.toString() method Symbol.unscopables – specifies an object value containing keys that are excluded from the "with" statement environment bindings Symbol.toPrimitive – specifies a function valued property that is called to convert an object to a corresponding primitive value Symbol.species – specifies a function-valued property that the constructor function uses to create derived objects
  • 62.
    const range =(start, end, step = 1) => ({ *[Symbol.iterator]() { for (let i = start; i <= end; i += step) { yield i; } } }); for (const n of range(10, 20)) { console.log(n); // => 10...20 }
  • 63.
    class User { constructor(name,age, password) { this.name = name; this.age = age; this.password = password; } [Symbol.toPrimitive](hint) { if (hint === 'number') { return this.age; } if (hint === 'string') { return `${this.name} (${this.age} years old)`; } return this; } } const user = new User('Charles Gibson', 54, 'SeCreT-PassW0rd');
 console.log(+user); // => 54 console.log(`${user}`); // => Charles Gibson (54 years old)
 console.log(user); // => User { name: 'Charles Gibson', age: 54, password: 'SeCreT-PassW0rd' }
  • 64.
    const inspect =Symbol.for('nodejs.util.inspect.custom'); class User { static [Symbol.hasInstance](instance) { if (instance === this) return true; if (instance.password === '*****') return true; return false; } [inspect]() { const userFakePassword = { ...this, password: '*****', }; Object.defineProperty(userFakePassword, 'constructor', { value: User, enumerable: false, }); return userFakePassword; } } const user = new User('Charles Gibson', 54, 'SeCreT-PassW0rd'); console.log(user); // => { name: 'Charles Gibson', age: 54, password: '*****' }
  • 65.
    Reflection in JavaScript constexampleObject = { a: 1, b: 2 }; Object.assign({ a: 0 }, { b: 1 }); // => { a: 0, b: 1 } Object.assign({}, 'abc'); // => { '0': 'a', '1': 'b', '2': 'c' } Object.create({}); Object.create(null); Object.fromEntries([['a', 1], ['b', 2]]); // => { a: 1, b: 2 } Object.setPrototypeOf(exampleObject, {}); Reflect.setPrototypeOf(exampleObject, {}); Object.defineProperty(exampleObject, 'c', { value: 4 }); Reflect.defineProperty(exampleObject, 'c', { value: 4 }); Object.defineProperties(exampleObject, { d: { value: 5 } });
  • 66.
    Reflection in JavaScript classExample { a = 1; b = 2; } const example = new Example(); example; // => Example { a: 1, b: 2 } example.constructor; // => [class Example] example instanceof Example; // => true Object.assign(example, { c: 10 }); example; // => Example { a: 1, b: 2, c: 10 } example.constructor; // => [class Example] example instanceof Example; // => true
  • 67.
    Reflection in JavaScript Object.freeze(exampleObject); Object.preventExtensions(exampleObject); Reflect.preventExtensions(exampleObject); Object.seal(exampleObject); Reflect.construct(Set,[[1, 2, 3]]); // = new Set([1, 2, 3]) Reflect.deleteProperty(exampleObject, 'a'); // = delete exampleObject.a; Reflect.get(exampleObject, 'b'); // = exampleObject.b Reflect.set(exampleObject, 'a', 1); // equals exampleObject.a = 1
  • 68.
    Interception in JavaScript newProxy(target, handler) handler.apply() handler.construct() handler.defineProperty() handler.deleteProperty() handler.get() handler.getOwnPropertyDescriptor() handler.getPrototypeOf() handler.has() handler.isExtensible() handler.ownKeys() handler.preventExtensions() handler.set() handler.setPrototypeOf()
  • 69.
    Performance comparison obj.get x931,245,919 ops/sec ±1.49% (85 runs sampled) Proxy.get x 23,572,175 ops/sec ±0.64% (87 runs sampled) obj.get is faster by 40x 0 250,000,000 500,000,000 750,000,000 1,000,000,000 obj.get Proxy.get 23,572,175 931,245,919 ops/sec
  • 70.
    Interception in JavaScript console.log(15in range(10, 20)); for (const n of range(10, 20)) { console.log(n); }
  • 71.
    Interception in JavaScript constiterator = { *[Symbol.iterator]() { for (let i = start; i <= end; i += step) { yield i; } } }; const handlers = { has(target, key) { if (typeof key === 'symbol') { return Reflect.has(target, key); } let n = Number(key); // cast type to number if (n >= start && n <= end) { return true; } return false; } }; const range = (start, end, step = 1) => { return new Proxy(iterator, handlers); };
  • 72.
  • 73.
    import css from'./sample.css'; console.dir(css); .hello { content: 'Hello, World!'; color: cornflowerblue; background-color: rgba(25, 25, 125, 1); text-align: center; } .wow { animation: linear 0.1s linear; } import { URL, pathToFileURL } from 'url'; import { cwd } from 'process'; import cssToJS from 'transform-css-to-js'; const baseURL = pathToFileURL(`${cwd()}/`).href; export function resolve(specifier, context, defaultResolve) { const { parentURL = baseURL } = context; if (specifier.endsWith('.css')) { return { url: new URL(specifier, parentURL).href }; } return defaultResolve(specifier, context, defaultResolve); } export async function load(url, context, defaultLoad) { if (url.endsWith('.css')) { const { source: rawSource } = await defaultLoad(url, { format: 'module' }); return { format: 'module', source: 'export default ' + cssToJS(rawSource.toString(), true), }; } return defaultLoad(url, context, defaultLoad); }
  • 74.
    function addNumbers(a: number,b: number) { return a + b; } var sum = addNumbers(10, 15) console.log('Sum of the two numbers is: ' + sum); node --experimental-loader=ts-node/esm example.ts
  • 75.
  • 76.
    function timerGame(callback) { console.log('Ready....go!'); setTimeout(()=> { console.log("Time's up -- stop!"); callback && callback(); }, 1000); } Mocks and stubs during testing // test jest.useFakeTimers(); jest.spyOn(global, 'setTimeout'); test('waits 1 second before ending the game', () => { const timerGame = require('../timerGame'); timerGame(); expect(setTimeout).toHaveBeenCalledTimes(1); expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000); });
  • 77.
    import * asreadline from 'node:readline/promises'; import { stdin as input, stdout as output } from 'process'; import express from 'express'; const app = express(); const cli = readline.createInterface({ input, output }); function displayCliMessage(...messages) { output.clearLine(-1); output.cursorTo(0); console.log(...messages); cli.prompt(); } app.locals.featureFlags = { shouldDisplayGreeting: true, }; app.get('/', (req, res, next) => { const { shouldDisplayGreeting } = res.app.locals.featureFlags; displayCliMessage('shouldDisplayGreeting', shouldDisplayGreeting); res.setHeader('Content-Type', 'text/plain'); if (shouldDisplayGreeting) { const userName = req.query?.name ?? 'Anonymous'; return res.end(`Greetings, ${userName}! It's hello from server!`); } return res.end('Hello from server!'); }); app.listen(3000, () => { displayCliMessage('Server is listening on port 3000'); }); cli.on('line', (command) => { if (command === 'toggle') { const { shouldDisplayGreeting } = app.locals.featureFlags; app.locals.featureFlags.shouldDisplayGreeting = !shouldDisplayGreeting; displayCliMessage('"shouldDisplayGreeting" flag is toggled!'); } }); Feature flags
  • 78.
    Function arguments validation constvalidatedFunction = (userName /* string */, userAge /* number */, callback /* function */) => { // here we are sure that arguments are of a valid type };
  • 79.
    Function arguments validation constvalidatedFunction = validate((userName /* string */, userAge /* number */, callback /* function */) => { // here we are sure that arguments are of a valid type });
  • 80.
    const validate =(fn) => { const functionSourceCode = fn.toString(); const tokens = tokenize(functionSourceCode); const annotations = tokens.filter(token => token.type === 'annotation'); return (...args) => { if (args.some((arg, i) => typeof arg !== annotations[i].token)) { throw new Error('invalid arguments'); } return fn(...args); }; }
  • 81.
    const tokenize =(functionSourceCode) => { const buffer = Buffer.from(functionSourceCode); const bufferLength = buffer.length; let parserMode = PARSER_MODES.ARGUMENT; let handler = PARSER_MODE_HANDLERS[parserMode]; const ctx = { charCode: 0, tokens: [], index: buffer.indexOf(40) + 1, // '(' isToken: false, tokenStart: 0, buffer, }; let parenthesisBalanceCount = ctx.index > 0 ? 1 : 0; while (parenthesisBalanceCount > 0 && ctx.index < bufferLength) { ctx.charCode = ctx.buffer[ctx.index]; handler = PARSER_MODE_HANDLERS[parserMode]; parserMode = handler(ctx); if (ctx.charCode === 40 /* ( */) { ++parenthesisBalanceCount; } else if (ctx.charCode === 41 /* ) */ && parserMode === PARSER_MODES.ARGUMENT) { --parenthesisBalanceCount; } if (parenthesisBalanceCount === 0) break; ++ctx.index; } return ctx.tokens; };
  • 82.
    PARSER_MODE_HANDLERS[PARSER_MODES.ARGUMENT] = (ctx)=> { if (ctx.charCode === 47) { // '/' const nextCharCode = ctx.buffer[ctx.index + 1]; if (nextCharCode === 42) { // '*' return PARSER_MODES.MULTILINE_ANNOTATION; } if (nextCharCode === 47) { // '/' return PARSER_MODES.LINE_ANNOTATION; } } if (!ctx.isToken && isAlphaNumeric(ctx.charCode)) { ctx.tokenStart = ctx.index; ctx.isToken = true; } else if (ctx.isToken && !isAlphaNumeric(ctx.charCode)) { const token = ctx.buffer.toString(undefined, ctx.tokenStart, ctx.index); ctx.tokens.push({ type: 'argument', token }); ctx.isToken = false; } return PARSER_MODES.ARGUMENT; };
  • 83.
    Find out moreon my GitHub
  • 84.