Skip to content

Commit

Permalink
Swap to use internal IsPlainObject utility rather than lodash/isPlain…
Browse files Browse the repository at this point in the history
…Object for performance reasons. See #2214.
  • Loading branch information
zachleat committed Feb 15, 2022
1 parent 900eec4 commit 3a7f568
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 11 deletions.
6 changes: 3 additions & 3 deletions src/ComputedDataProxy.js
@@ -1,6 +1,6 @@
const lodashSet = require("lodash/set");
const lodashGet = require("lodash/get");
const lodashIsPlainObject = require("lodash/isPlainObject");
const isPlainObject = require("./Util/isPlainObject");

/* Calculates computed data using Proxies */
class ComputedDataProxy {
Expand All @@ -13,7 +13,7 @@ class ComputedDataProxy {
}

isArrayOrPlainObject(data) {
return Array.isArray(data) || lodashIsPlainObject(data);
return Array.isArray(data) || isPlainObject(data);
}

getProxyData(data, keyRef) {
Expand Down Expand Up @@ -97,7 +97,7 @@ class ComputedDataProxy {
}

_getProxyData(data, keyRef, parentKey = "") {
if (lodashIsPlainObject(data)) {
if (isPlainObject(data)) {
return this._getProxyForObject(data, keyRef, parentKey);
} else if (Array.isArray(data)) {
return this._getProxyForArray(data, keyRef, parentKey);
Expand Down
6 changes: 3 additions & 3 deletions src/Plugins/RenderPlugin.js
@@ -1,6 +1,6 @@
const fs = require("fs");
const fsp = fs.promises;
const lodashIsPlainObject = require("lodash/isPlainObject");
const isPlainObject = require("../Util/IsPlainObject");

// TODO add a first-class Markdown component to expose this using Markdown-only syntax (will need to be synchronous for markdown-it)

Expand Down Expand Up @@ -276,7 +276,7 @@ function EleventyPlugin(eleventyConfig, options = {}) {
}

// if the user passes a string or other literal, remap to an object.
if (!lodashIsPlainObject(data)) {
if (!isPlainObject(data)) {
data = {
_: data,
};
Expand Down Expand Up @@ -309,7 +309,7 @@ function EleventyPlugin(eleventyConfig, options = {}) {
}

// if the user passes a string or other literal, remap to an object.
if (!lodashIsPlainObject(data)) {
if (!isPlainObject(data)) {
data = {
_: data,
};
Expand Down
2 changes: 1 addition & 1 deletion src/Template.js
Expand Up @@ -6,7 +6,7 @@ const mkdir = util.promisify(fs.mkdir);
const os = require("os");
const path = require("path");
const normalize = require("normalize-path");
const isPlainObject = require("lodash/isPlainObject");
const isPlainObject = require("./Util/IsPlainObject");
const lodashGet = require("lodash/get");
const lodashSet = require("lodash/set");
const { DateTime } = require("luxon");
Expand Down
2 changes: 1 addition & 1 deletion src/TemplateBehavior.js
@@ -1,4 +1,4 @@
const isPlainObject = require("lodash/isPlainObject");
const isPlainObject = require("./Util/IsPlainObject");

class TemplateBehavior {
constructor(config) {
Expand Down
2 changes: 1 addition & 1 deletion src/TemplateMap.js
@@ -1,4 +1,4 @@
const isPlainObject = require("lodash/isPlainObject");
const isPlainObject = require("./Util/IsPlainObject");
const DependencyGraph = require("dependency-graph").DepGraph;
const TemplateCollection = require("./TemplateCollection");
const EleventyErrorUtil = require("./EleventyErrorUtil");
Expand Down
2 changes: 1 addition & 1 deletion src/TemplatePermalink.js
@@ -1,7 +1,7 @@
const path = require("path");
const TemplatePath = require("./TemplatePath");
const normalize = require("normalize-path");
const isPlainObject = require("lodash/isPlainObject");
const isPlainObject = require("./Util/IsPlainObject");
const serverlessUrlFilter = require("./Filters/ServerlessUrl");

class TemplatePermalink {
Expand Down
24 changes: 24 additions & 0 deletions src/Util/IsPlainObject.js
@@ -0,0 +1,24 @@
/* Prior art: this utility was created for https://github.com/11ty/eleventy/issues/2214
* Inspired by implementations from `is-what`, `typechecker`, `jQuery`, and `lodash`
* `is-what`
* More reading at https://www.npmjs.com/package/is-what#user-content-isplainobject-vs-isanyobject
* if (Object.prototype.toString.call(value).slice(8, -1) !== 'Object') return false;
* return value.constructor === Object && Object.getPrototypeOf(value) === Object.prototype;
* `typechecker`
* return value !== null && typeof value === 'object' && value.__proto__ === Object.prototype;
* Notably jQuery and lodash have very similar implementations.
* For later, remember the `value === Object(value)` trick
*/

module.exports = function (value) {
if (value === null || typeof value !== "object") {
return false;
}
let proto = Object.getPrototypeOf(value);
return !proto || proto === Object.prototype;
};
2 changes: 1 addition & 1 deletion src/Util/Merge.js
@@ -1,4 +1,4 @@
const isPlainObject = require("lodash/isPlainObject");
const isPlainObject = require("../Util/IsPlainObject");
const OVERRIDE_PREFIX = "override:";

function getMergedItem(target, source, parentKey) {
Expand Down
80 changes: 80 additions & 0 deletions test/IsPlainObjectTest.js
@@ -0,0 +1,80 @@
const test = require("ava");
const isPlainObject = require("../src/Util/IsPlainObject");

test("isPlainObject", (t) => {
t.is(isPlainObject(null), false);
t.is(isPlainObject(undefined), false);
t.is(isPlainObject(1), false);
t.is(isPlainObject(true), false);
t.is(isPlainObject(false), false);
t.is(isPlainObject("string"), false);
t.is(isPlainObject([]), false);
t.is(isPlainObject(Symbol("a")), false);
t.is(
isPlainObject(function () {}),
false
);
});

// https://github.com/lodash/lodash/blob/ddfd9b11a0126db2302cb70ec9973b66baec0975/test/test.js#L11447
// Notably, did not include the test for DOM Elements.
test("Test from lodash.isPlainObject", (t) => {
t.is(isPlainObject({}), true);
t.is(isPlainObject({ a: 1 }), true);

function Foo(a) {
this.a = 1;
}

t.is(isPlainObject({ constructor: Foo }), true);
t.is(isPlainObject([1, 2, 3]), false);
t.is(isPlainObject(new Foo(1)), false);
});

test("Test from lodash.isPlainObject: should return `true` for objects with a `[[Prototype]]` of `null`", (t) => {
let obj = Object.create(null);
t.is(isPlainObject(obj), true);

obj.constructor = Object.prototype.constructor;
t.is(isPlainObject(obj), true);
});

test("Test from lodash.isPlainObject: should return `true` for objects with a `valueOf` property", (t) => {
t.is(isPlainObject({ valueOf: 0 }), true);
});

test("Test from lodash.isPlainObject: should return `true` for objects with a writable `Symbol.toStringTag` property", (t) => {
let obj = {};
obj[Symbol.toStringTag] = "X";

t.is(isPlainObject(obj), true);
});

test("Test from lodash.isPlainObject: should return `false` for objects with a custom `[[Prototype]]`", (t) => {
let obj = Object.create({ a: 1 });
t.is(isPlainObject(obj), false);
});

test("Test from lodash.isPlainObject (modified): should return `false` for non-Object objects", (t) => {
t.is(isPlainObject(arguments), true); // WARNING: lodash was false
t.is(isPlainObject(Error), false);
t.is(isPlainObject(Math), true); // WARNING: lodash was false
});

test("Test from lodash.isPlainObject: should return `false` for non-objects", (t) => {
t.is(isPlainObject(true), false);
t.is(isPlainObject("a"), false);
t.is(isPlainObject(Symbol("a")), false);
});

test("Test from lodash.isPlainObject (modified): should return `true` for objects with a read-only `Symbol.toStringTag` property", (t) => {
var object = {};
Object.defineProperty(object, Symbol.toStringTag, {
configurable: true,
enumerable: false,
writable: false,
value: "X",
});

t.is(isPlainObject(object), true); // WARNING: lodash was false
});

0 comments on commit 3a7f568

Please sign in to comment.