Skip to content

Instantly share code, notes, and snippets.

@kentcdodds
Last active March 11, 2021 01:41
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save kentcdodds/5a13a838cb2c915a2fbcd780c5c0de50 to your computer and use it in GitHub Desktop.
Save kentcdodds/5a13a838cb2c915a2fbcd780c5c0de50 to your computer and use it in GitHub Desktop.
JavaScript Program Slicing with SliceJS
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var cov_1iywwpptmf = function () {
var path = '/Users/kdodds/Developer/slice-js/app/example-code/utils.js',
hash = '3e246bbf8c606fc6489201163dac20a5b45b64ad',
global = new Function('return this')(),
gcv = '__coverage__',
coverageData = {
path: '/Users/kdodds/Developer/slice-js/app/example-code/utils.js',
statementMap: {
'0': {
start: {
line: 4,
column: 2
},
end: {
line: 4,
column: 18
}
},
'1': {
start: {
line: 6,
column: 2
},
end: {
line: 15,
column: 4
}
},
'2': {
start: {
line: 7,
column: 4
},
end: {
line: 14,
column: 5
}
},
'3': {
start: {
line: 13,
column: 6
},
end: {
line: 13,
column: 25
}
},
'4': {
start: {
line: 17,
column: 2
},
end: {
line: 17,
column: 10
}
},
'5': {
start: {
line: 22,
column: 2
},
end: {
line: 24,
column: 3
}
},
'6': {
start: {
line: 23,
column: 4
},
end: {
line: 23,
column: 15
}
},
'7': {
start: {
line: 25,
column: 15
},
end: {
line: 25,
column: 26
}
},
'8': {
start: {
line: 26,
column: 17
},
end: {
line: 26,
column: 53
}
},
'9': {
start: {
line: 27,
column: 22
},
end: {
line: 27,
column: 62
}
},
'10': {
start: {
line: 28,
column: 15
},
end: {
line: 28,
column: 19
}
},
'11': {
start: {
line: 30,
column: 2
},
end: {
line: 56,
column: 3
}
},
'12': {
start: {
line: 31,
column: 4
},
end: {
line: 55,
column: 5
}
},
'13': {
start: {
line: 32,
column: 6
},
end: {
line: 32,
column: 17
}
},
'14': {
start: {
line: 33,
column: 6
},
end: {
line: 35,
column: 8
}
},
'15': {
start: {
line: 34,
column: 8
},
end: {
line: 34,
column: 36
}
},
'16': {
start: {
line: 36,
column: 11
},
end: {
line: 55,
column: 5
}
},
'17': {
start: {
line: 37,
column: 6
},
end: {
line: 54,
column: 7
}
},
'18': {
start: {
line: 38,
column: 8
},
end: {
line: 38,
column: 37
}
},
'19': {
start: {
line: 39,
column: 13
},
end: {
line: 54,
column: 7
}
},
'20': {
start: {
line: 40,
column: 8
},
end: {
line: 49,
column: 9
}
},
'21': {
start: {
line: 41,
column: 10
},
end: {
line: 41,
column: 33
}
},
'22': {
start: {
line: 43,
column: 10
},
end: {
line: 43,
column: 21
}
},
'23': {
start: {
line: 44,
column: 10
},
end: {
line: 48,
column: 11
}
},
'24': {
start: {
line: 45,
column: 12
},
end: {
line: 47,
column: 13
}
},
'25': {
start: {
line: 46,
column: 14
},
end: {
line: 46,
column: 40
}
},
'26': {
start: {
line: 50,
column: 13
},
end: {
line: 54,
column: 7
}
},
'27': {
start: {
line: 51,
column: 8
},
end: {
line: 51,
column: 39
}
},
'28': {
start: {
line: 53,
column: 8
},
end: {
line: 53,
column: 21
}
},
'29': {
start: {
line: 58,
column: 2
},
end: {
line: 58,
column: 15
}
}
},
fnMap: {
'0': {
name: 'deepFreeze',
decl: {
start: {
line: 3,
column: 9
},
end: {
line: 3,
column: 19
}
},
loc: {
start: {
line: 3,
column: 23
},
end: {
line: 18,
column: 1
}
}
},
'1': {
name: '(anonymous_1)',
decl: {
start: {
line: 6,
column: 40
},
end: {
line: 6,
column: 41
}
},
loc: {
start: {
line: 6,
column: 48
},
end: {
line: 15,
column: 3
}
}
},
'2': {
name: 'clone',
decl: {
start: {
line: 20,
column: 9
},
end: {
line: 20,
column: 14
}
},
loc: {
start: {
line: 20,
column: 21
},
end: {
line: 59,
column: 1
}
}
},
'3': {
name: '(anonymous_3)',
decl: {
start: {
line: 33,
column: 19
},
end: {
line: 33,
column: 20
}
},
loc: {
start: {
line: 33,
column: 37
},
end: {
line: 35,
column: 7
}
}
}
},
branchMap: {
'0': {
loc: {
start: {
line: 7,
column: 4
},
end: {
line: 14,
column: 5
}
},
type: 'if',
locations: [{
start: {
line: 7,
column: 4
},
end: {
line: 14,
column: 5
}
}, {
start: {
line: 7,
column: 4
},
end: {
line: 14,
column: 5
}
}]
},
'1': {
loc: {
start: {
line: 8,
column: 6
},
end: {
line: 11,
column: 31
}
},
type: 'binary-expr',
locations: [{
start: {
line: 8,
column: 6
},
end: {
line: 8,
column: 28
}
}, {
start: {
line: 9,
column: 6
},
end: {
line: 9,
column: 22
}
}, {
start: {
line: 10,
column: 7
},
end: {
line: 10,
column: 34
}
}, {
start: {
line: 10,
column: 38
},
end: {
line: 10,
column: 67
}
}, {
start: {
line: 11,
column: 6
},
end: {
line: 11,
column: 31
}
}]
},
'2': {
loc: {
start: {
line: 22,
column: 2
},
end: {
line: 24,
column: 3
}
},
type: 'if',
locations: [{
start: {
line: 22,
column: 2
},
end: {
line: 24,
column: 3
}
}, {
start: {
line: 22,
column: 2
},
end: {
line: 24,
column: 3
}
}]
},
'3': {
loc: {
start: {
line: 27,
column: 22
},
end: {
line: 27,
column: 62
}
},
type: 'binary-expr',
locations: [{
start: {
line: 27,
column: 22
},
end: {
line: 27,
column: 39
}
}, {
start: {
line: 27,
column: 43
},
end: {
line: 27,
column: 62
}
}]
},
'4': {
loc: {
start: {
line: 30,
column: 2
},
end: {
line: 56,
column: 3
}
},
type: 'if',
locations: [{
start: {
line: 30,
column: 2
},
end: {
line: 56,
column: 3
}
}, {
start: {
line: 30,
column: 2
},
end: {
line: 56,
column: 3
}
}]
},
'5': {
loc: {
start: {
line: 31,
column: 4
},
end: {
line: 55,
column: 5
}
},
type: 'if',
locations: [{
start: {
line: 31,
column: 4
},
end: {
line: 55,
column: 5
}
}, {
start: {
line: 31,
column: 4
},
end: {
line: 55,
column: 5
}
}]
},
'6': {
loc: {
start: {
line: 36,
column: 11
},
end: {
line: 55,
column: 5
}
},
type: 'if',
locations: [{
start: {
line: 36,
column: 11
},
end: {
line: 55,
column: 5
}
}, {
start: {
line: 36,
column: 11
},
end: {
line: 55,
column: 5
}
}]
},
'7': {
loc: {
start: {
line: 37,
column: 6
},
end: {
line: 54,
column: 7
}
},
type: 'if',
locations: [{
start: {
line: 37,
column: 6
},
end: {
line: 54,
column: 7
}
}, {
start: {
line: 37,
column: 6
},
end: {
line: 54,
column: 7
}
}]
},
'8': {
loc: {
start: {
line: 37,
column: 10
},
end: {
line: 37,
column: 63
}
},
type: 'binary-expr',
locations: [{
start: {
line: 37,
column: 10
},
end: {
line: 37,
column: 23
}
}, {
start: {
line: 37,
column: 27
},
end: {
line: 37,
column: 63
}
}]
},
'9': {
loc: {
start: {
line: 39,
column: 13
},
end: {
line: 54,
column: 7
}
},
type: 'if',
locations: [{
start: {
line: 39,
column: 13
},
end: {
line: 54,
column: 7
}
}, {
start: {
line: 39,
column: 13
},
end: {
line: 54,
column: 7
}
}]
},
'10': {
loc: {
start: {
line: 40,
column: 8
},
end: {
line: 49,
column: 9
}
},
type: 'if',
locations: [{
start: {
line: 40,
column: 8
},
end: {
line: 49,
column: 9
}
}, {
start: {
line: 40,
column: 8
},
end: {
line: 49,
column: 9
}
}]
},
'11': {
loc: {
start: {
line: 45,
column: 12
},
end: {
line: 47,
column: 13
}
},
type: 'if',
locations: [{
start: {
line: 45,
column: 12
},
end: {
line: 47,
column: 13
}
}, {
start: {
line: 45,
column: 12
},
end: {
line: 47,
column: 13
}
}]
},
'12': {
loc: {
start: {
line: 50,
column: 13
},
end: {
line: 54,
column: 7
}
},
type: 'if',
locations: [{
start: {
line: 50,
column: 13
},
end: {
line: 54,
column: 7
}
}, {
start: {
line: 50,
column: 13
},
end: {
line: 54,
column: 7
}
}]
}
},
s: {
'0': 0,
'1': 0,
'2': 0,
'3': 0,
'4': 0,
'5': 0,
'6': 0,
'7': 0,
'8': 0,
'9': 0,
'10': 0,
'11': 0,
'12': 0,
'13': 0,
'14': 0,
'15': 0,
'16': 0,
'17': 0,
'18': 0,
'19': 0,
'20': 0,
'21': 0,
'22': 0,
'23': 0,
'24': 0,
'25': 0,
'26': 0,
'27': 0,
'28': 0,
'29': 0
},
f: {
'0': 0,
'1': 0,
'2': 0,
'3': 0
},
b: {
'0': [0, 0],
'1': [0, 0, 0, 0, 0],
'2': [0, 0],
'3': [0, 0],
'4': [0, 0],
'5': [0, 0],
'6': [0, 0],
'7': [0, 0],
'8': [0, 0],
'9': [0, 0],
'10': [0, 0],
'11': [0, 0],
'12': [0, 0]
},
_coverageSchema: '332fd63041d2c1bcb487cc26dd0d5f7d97098a6c'
},
coverage = global[gcv] || (global[gcv] = {});
if (coverage[path] && coverage[path].hash === hash) {
return coverage[path];
}
coverageData.hash = hash;
return coverage[path] = coverageData;
}();
exports.deepFreeze = deepFreeze;
exports.clone = clone;
function deepFreeze(o) {
++cov_1iywwpptmf.f[0];
++cov_1iywwpptmf.s[0];
Object.freeze(o);
++cov_1iywwpptmf.s[1];
Object.getOwnPropertyNames(o).forEach(prop => {
++cov_1iywwpptmf.f[1];
++cov_1iywwpptmf.s[2];
if ((++cov_1iywwpptmf.b[1][0], o.hasOwnProperty(prop)) && (++cov_1iywwpptmf.b[1][1], o[prop] !== null) && ((++cov_1iywwpptmf.b[1][2], typeof o[prop] === 'object') || (++cov_1iywwpptmf.b[1][3], typeof o[prop] === 'function')) && (++cov_1iywwpptmf.b[1][4], !Object.isFrozen(o[prop]))) {
++cov_1iywwpptmf.b[0][0];
++cov_1iywwpptmf.s[3];
deepFreeze(o[prop]);
} else {
++cov_1iywwpptmf.b[0][1];
}
});
++cov_1iywwpptmf.s[4];
return o;
}
function clone(item) {
++cov_1iywwpptmf.f[2];
++cov_1iywwpptmf.s[5];
/* eslint complexity:[2, 11] max-depth:[2, 6] */
if (!item) {
++cov_1iywwpptmf.b[2][0];
++cov_1iywwpptmf.s[6];
return item;
} else {
++cov_1iywwpptmf.b[2][1];
}
const type = (++cov_1iywwpptmf.s[7], typeof item);
const string = (++cov_1iywwpptmf.s[8], Object.prototype.toString.call(item));
const isPrimitive = (++cov_1iywwpptmf.s[9], (++cov_1iywwpptmf.b[3][0], type !== 'object') && (++cov_1iywwpptmf.b[3][1], type !== 'function'));
let result = (++cov_1iywwpptmf.s[10], item);
++cov_1iywwpptmf.s[11];
if (!isPrimitive) {
++cov_1iywwpptmf.b[4][0];
++cov_1iywwpptmf.s[12];
if (string === '[object Array]') {
++cov_1iywwpptmf.b[5][0];
++cov_1iywwpptmf.s[13];
result = [];
++cov_1iywwpptmf.s[14];
item.forEach((child, index) => {
++cov_1iywwpptmf.f[3];
++cov_1iywwpptmf.s[15];
result[index] = clone(child);
});
} else {
++cov_1iywwpptmf.b[5][1];
++cov_1iywwpptmf.s[16];
if (type === 'object') {
++cov_1iywwpptmf.b[6][0];
++cov_1iywwpptmf.s[17];
if ((++cov_1iywwpptmf.b[8][0], item.nodeType) && (++cov_1iywwpptmf.b[8][1], typeof item.cloneNode === 'function')) {
++cov_1iywwpptmf.b[7][0];
++cov_1iywwpptmf.s[18];
result = item.cloneNode(true);
} else {
++cov_1iywwpptmf.b[7][1];
++cov_1iywwpptmf.s[19];
if (!item.prototype) {
++cov_1iywwpptmf.b[9][0];
++cov_1iywwpptmf.s[20];
if (string === '[object Date]') {
++cov_1iywwpptmf.b[10][0];
++cov_1iywwpptmf.s[21];
result = new Date(item);
} else {
++cov_1iywwpptmf.b[10][1];
++cov_1iywwpptmf.s[22];
result = {};
++cov_1iywwpptmf.s[23];
for (const i in item) {
++cov_1iywwpptmf.s[24];
if (item.hasOwnProperty(i)) {
++cov_1iywwpptmf.b[11][0];
++cov_1iywwpptmf.s[25];
result[i] = clone(item[i]);
} else {
++cov_1iywwpptmf.b[11][1];
}
}
}
} else {
++cov_1iywwpptmf.b[9][1];
++cov_1iywwpptmf.s[26];
if (item.constructor) {
++cov_1iywwpptmf.b[12][0];
++cov_1iywwpptmf.s[27];
result = new item.constructor();
} else {
++cov_1iywwpptmf.b[12][1];
++cov_1iywwpptmf.s[28];
result = item;
}
}
}
} else {
++cov_1iywwpptmf.b[6][1];
}
}
} else {
++cov_1iywwpptmf.b[4][1];
}
++cov_1iywwpptmf.s[29];
return result;
}

Learning Code

One of the original goals to this project is to help developers learn code. Take this module for example:

export {deepFreeze, clone}

function deepFreeze(o) {
  Object.freeze(o)

  Object.getOwnPropertyNames(o).forEach(prop => {
    if (
      o.hasOwnProperty(prop) &&
      o[prop] !== null &&
      (typeof o[prop] === 'object' || typeof o[prop] === 'function') &&
      !Object.isFrozen(o[prop])
    ) {
      deepFreeze(o[prop])
    }
  })

  return o
}

function clone(item) {
  /* eslint complexity:[2, 11] max-depth:[2, 6] */
  if (!item) {
    return item
  }
  const type = typeof item
  const string = Object.prototype.toString.call(item)
  const isPrimitive = type !== 'object' && type !== 'function'
  let result = item

  if (!isPrimitive) {
    if (string === '[object Array]') {
      result = []
      item.forEach((child, index) => {
        result[index] = clone(child)
      })
    } else if (type === 'object') {
      if (item.nodeType && typeof item.cloneNode === 'function') {
        result = item.cloneNode(true)
      } else if (!item.prototype) {
        if (string === '[object Date]') {
          result = new Date(item)
        } else {
          result = {}
          for (const i in item) {
            if (item.hasOwnProperty(i)) {
              result[i] = clone(item[i])
            }
          }
        }
      } else if (item.constructor) {
        result = new item.constructor()
      } else {
        result = item
      }
    }
  }

  return result
}

The clone function is 39 lines of code, has a nesting depth of 6, and a cyclomatic complexity of 11. Not exactly the most simple code in the world! All the branches are important and handle edge cases that make the code work. In addition, there is another function in the file and without inspecting the code you're unsure whether that function is useful in the clone method or not. All of this ends up making learning how clone works at least a 10 minute task. But with slice-js, you can learn it much more quickly! Let's use slice-js to learn this code.

slice-js takes two inputs: The source code, and a code coverage report (for example). Based on this information, it can create a slice of the program that's relevant for that coverage. Let's just say that we can generate the coverage based on a given usage module. We'll start with the most basic usage of all. Passing nothing:

import {clone} from 'clone'
clone()

Based on this usage, a coverage report could be generated and the resulting code slice would look much easier to learn quickly:

export {clone}

function clone(item) {
  return item
}

We've gone from 38 lines of code to 1 and the cyclomatic complexity from 1 to 1. That's considerably more easy to learn! But that's not everything that's important in this code. The original code is definitely important. So let's add more use-cases and see how this slice is changed.

import {clone} from 'clone'
clone()
clone('hi')

With that addition of clone('hi'), we'll get this difference:

export {clone}

function clone(item) {
+ if (!item) {
+   return item
+ }
+
  return item
}

That's pretty reasonable to learn in addition to what we've already learned about this code. Let's add more now:

import {clone} from 'clone'
clone()
clone('hi')
clone({name: 'Luke'})

And here's what the slice looks like now:

export {clone}

function clone(item) {
  if (!item) {
    return item
  }
+  const type = typeof item
+
+  let result = item
+
+  if (!(type !== 'object' && type !== 'function')) {
+    result = {}
+    for (const i in item) {
+      result[i] = clone(item[i])
+    }
+  }

  return result
}

Let's do this one more time:

import {clone} from 'clone'
clone('hello')
clone(null)
clone({name: 'Luke'})
clone({friends: [{name: 'Rebecca'}]})

And with that, we add yet another edge case.

export {clone}

function clone(item) {
  if (!item) {
    return item
  }
  const type = typeof item
+  const string = Object.prototype.toString.call(item)

  let result = item

  if (!(type !== 'object' && type !== 'function')) {
+    if (string === '[object Array]') {
+      result = []
+      item.forEach((child, index) => {
+        result[index] = clone(child)
+      })
+    } else {
      result = {}
      for (const i in item) {
        result[i] = clone(item[i])
      }
+    }
  }

  return result
}

The benefit of this approach is that we learn the code use-case-by-use-case. It's much easier to learn bit by bit like this, and slice-js enables this.

Ultra-Tree Shaking ™

Tree shaking is a super cool concept. Here's a basic example of tree shaking from Webpack or Rollup:

math.js

export {doMath, sayMath}

const add = (a, b) => a + b
const subtract = (a, b) => a - b
const divide = (a, b) => a / b
const multiply = (a, b) => a * b

function doMath(a, b, operation) {
  switch (operation) {
    case 'add':
      return add(a, b)
    case 'subtract':
      return subtract(a, b)
    case 'divide':
      return divide(a, b)
    case 'multiply':
      return multiply(a, b)
    default:
      throw new Error(`Unsupported operation: ${operation}`)
  }
}

function sayMath() {
  return 'MATH!'
}

app.js

import {doMath}
doMath(2, 3, 'multiply') // 6

The tree-shaken result of math.js would effectively be:

export {doMath}

const add = (a, b) => a + b
const subtract = (a, b) => a - b
const divide = (a, b) => a / b
const multiply = (a, b) => a * b

function doMath(a, b, operation) {
  switch (operation) {
    case 'add':
      return add(a, b)
    case 'subtract':
      return subtract(a, b)
    case 'divide':
      return divide(a, b)
    case 'multiply':
      return multiply(a, b)
    default:
      throw new Error(`Unsupported operation: ${operation}`)
  }
}

However, with SliceJS, we could remove even more code. Like this:

export {doMath}

const multiply = (a, b) => a * b

function doMath(a, b) {
  return multiply(a, b)
}

Imagine doing this with lodash, jquery or react! Could be some pretty serious savings!

The biggest challenge with this would be getting an accurate measure of code coverage. For most applications, you'd have a hard time making sure that your tests cover all use cases, and if you slice code out that's not covered by your test cases, then your users wont get that code and things will blow up. There's still more work to be done here, but I think that it's possible to make a big difference!

Shaking data

Another thing that I think would be super cool to do would be to not allocate memory for objects that are never used. Right now, with SliceJS, here's an example that could be further optimized:

log.js

const currentLevel = 0

const logLevels = {
  ALL: 100,
  DEBUG: 70,
  ERROR: 50,
  INFO: 30,
  WARN: 20,
  OFF: 0,
}

const setCurrentLevel = level => currentLevel = level

export {log, setCurrentLevel, logLevels}

function log(level, ...args) {
  if (currentLevel > level) {
    console.log(...args)
  }
}

app.js

import {log, logLevels, setCurrentLevel}
setCurrentLevel(logLevels.ERROR)
log(logLevels.WARN, 'This is a warning!')

If we tracked data coverage (in addition to branch/function/statement coverage as we do now), then we could slice out the allocation for some of the properties in the logLevels object as well! This would result in:

const currentLevel = 0

const logLevels = {
  ERROR: 50,
  WARN: 20,
}

const setCurrentLevel = level => currentLevel = level

export {log, setCurrentLevel, logLevels}

function log(level, ...args) {
  if (currentLevel > level) {
    console.log(...args)
  }
}

Which would be even cooler in scenarios where the objects are actually significant in length and amount of memory!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment