Skip to content

Commit d2154f1

Browse files
authored
Replace lodash with native JS equivalents (#522)
* Replace lodash with native JS equivalents Removes lodash (and @types/lodash) as a dependency. All usages are replaced with native ES2020 equivalents: optional chaining, Array methods, Object.entries/spread, and typeof checks. A shared isPlainObject helper is extracted to src/utils/objectutils.js. * Convert objectutils.js to TypeScript Replaces the CommonJS module with a typed ES module export. Adds a `val is Record<string, unknown>` type predicate so callers get proper type narrowing when using `isPlainObject`. * Add license header to objectutils.ts
1 parent bb4185e commit d2154f1

File tree

12 files changed

+72
-61
lines changed

12 files changed

+72
-61
lines changed

package-lock.json

Lines changed: 0 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@
5959
"glob-slasher": "^1.0.1",
6060
"is-url": "^1.2.2",
6161
"join-path": "^1.1.1",
62-
"lodash": "^4.17.19",
6362
"mime-types": "^2.1.35",
6463
"minimatch": "^6.1.6",
6564
"morgan": "^1.8.2",
@@ -77,7 +76,6 @@
7776
"@types/chai": "^4.3.3",
7877
"@types/chai-as-promised": "^7.1.5",
7978
"@types/connect": "^3.4.35",
80-
"@types/lodash": "^4.14.186",
8179
"@types/mime-types": "^2.1.1",
8280
"@types/mocha": "^10.0.0",
8381
"@types/node": "^24.3.1",

src/activator.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,14 @@
2020
*/
2121

2222
const middleware = require("./middleware");
23-
const _ = require("lodash");
2423
const promiseback = require("./utils/promiseback");
2524

2625
const Activator = function (spec, provider) {
2726
this.spec = spec;
2827
this.provider = provider;
2928
this.stack = this.buildStack();
3029

31-
if (_.isFunction(spec.config)) {
30+
if (typeof spec.config === "function") {
3231
this.awaitConfig = spec.config;
3332
} else {
3433
this.awaitConfig = function () {
@@ -41,16 +40,16 @@ Activator.prototype.buildStack = function () {
4140
const self = this;
4241

4342
const stack = this.spec.stack.slice(0);
44-
_.forEach(this.spec.before, (wares, name) => {
43+
Object.entries(this.spec.before ?? {}).forEach(([name, wares]) => {
4544
stack.splice(...[stack.indexOf(name), 0].concat(wares));
4645
});
4746

48-
_.forEach(this.spec.after, (wares, name) => {
47+
Object.entries(this.spec.after ?? {}).forEach(([name, wares]) => {
4948
stack.splice(...[stack.indexOf(name) + 1, 0].concat(wares));
5049
});
5150

5251
return stack.map((ware) => {
53-
return _.isFunction(ware) ? ware : middleware[ware](self.spec);
52+
return typeof ware === "function" ? ware : middleware[ware](self.spec);
5453
});
5554
};
5655

src/loaders/config-file.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@
2121

2222
const fs = require("fs");
2323

24-
const _ = require("lodash");
2524
const join = require("join-path");
2625
const path = require("path");
26+
const { isPlainObject } = require("../utils/objectutils");
2727

2828
const CONFIG_FILE = ["superstatic.json", "firebase.json"];
2929

3030
module.exports = function (filename) {
31-
if (_.isFunction(filename)) {
31+
if (typeof filename === "function") {
3232
return filename;
3333
}
3434

@@ -41,26 +41,26 @@ module.exports = function (filename) {
4141
try {
4242
configObject = JSON.parse(filename);
4343
} catch {
44-
if (_.isPlainObject(filename)) {
44+
if (isPlainObject(filename)) {
4545
configObject = filename;
4646
filename = CONFIG_FILE;
4747
}
4848
}
4949

50-
if (_.isArray(filename)) {
51-
filename = _.find(filename, (name) => {
50+
if (Array.isArray(filename)) {
51+
filename = filename.find((name) => {
5252
return fs.existsSync(join(process.cwd(), name));
5353
});
5454
}
5555

5656
// Set back to default config file if stringified object is
5757
// given as config. With this, we override values in the config file
58-
if (_.isPlainObject(filename)) {
58+
if (isPlainObject(filename)) {
5959
filename = CONFIG_FILE;
6060
}
6161

6262
// A file name or array of file names
63-
if (_.isString(filename) && _.endsWith(filename, "json")) {
63+
if (typeof filename === "string" && filename.endsWith("json")) {
6464
try {
6565
config = JSON.parse(fs.readFileSync(path.resolve(filename)));
6666
config = config.hosting ?? config;
@@ -71,5 +71,5 @@ module.exports = function (filename) {
7171

7272
// Passing an object as the config value merges
7373
// the config data
74-
return _.assign(config, configObject);
74+
return { ...config, ...configObject };
7575
};

src/middleware/files.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2020
*/
2121

22-
const _ = require("lodash");
2322
const { i18nContentOptions } = require("../utils/i18n");
2423
const pathutils = require("../utils/pathutils");
2524
const url = require("url");
@@ -42,17 +41,17 @@ module.exports = function () {
4241
const pathname = pathutils.normalizeMultiSlashes(parsedUrl.pathname);
4342
const search = parsedUrl.search ?? "";
4443

45-
const cleanUrlRules = !!_.get(req, "superstatic.cleanUrls");
44+
const cleanUrlRules = !!req?.superstatic?.cleanUrls;
4645

4746
// Exact file always wins.
4847
return providerResult(req, res, pathname)
4948
.then((result) => {
5049
if (result) {
5150
// If we are using cleanURLs, we'll trim off any `.html` (or `/index.html`), if it exists.
5251
if (cleanUrlRules) {
53-
if (_.endsWith(pathname, ".html")) {
52+
if (pathname.endsWith(".html")) {
5453
let redirPath = pathutils.removeTrailingString(pathname, ".html");
55-
if (_.endsWith(redirPath, "/index")) {
54+
if (redirPath.endsWith("/index")) {
5655
redirPath = pathutils.removeTrailingString(redirPath, "/index");
5756
}
5857
if (trailingSlashBehavior === true) {
@@ -154,7 +153,7 @@ module.exports = function () {
154153
});
155154
}
156155
// If we've gotten this far and still have `/index.html` on the end, we want to remove it from the URL.
157-
if (_.endsWith(appendedPath, "/index.html")) {
156+
if (appendedPath.endsWith("/index.html")) {
158157
return res.superstatic.handle({
159158
redirect: normalizeRedirectPath(
160159
pathutils.removeTrailingString(

src/middleware/headers.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,16 @@
1919
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2020
*/
2121

22-
const _ = require("lodash");
2322
const slasher = require("glob-slasher");
2423
const urlParser = require("url");
2524
const onHeaders = require("on-headers");
2625
const patterns = require("../utils/patterns");
2726

2827
const normalizedConfigHeaders = function (spec, config) {
2928
const out = config ?? [];
30-
if (_.isArray(config)) {
29+
if (Array.isArray(config)) {
3130
const _isAllowed = function (headerToSet) {
32-
return _.includes(spec.allowedHeaders, headerToSet.key.toLowerCase());
31+
return spec.allowedHeaders.includes(headerToSet.key.toLowerCase());
3332
};
3433

3534
for (const c of config) {
@@ -61,7 +60,7 @@ const matcher = function (configHeaders) {
6160

6261
module.exports = function (spec) {
6362
return function (req, res, next) {
64-
const config = _.get(req, "superstatic.headers");
63+
const config = req?.superstatic?.headers;
6564
if (!config) {
6665
return next();
6766
}
@@ -71,7 +70,7 @@ module.exports = function (spec) {
7170
const matches = headers(slasher(pathname));
7271

7372
onHeaders(res, () => {
74-
_.forEach(matches, (header) => {
73+
matches.forEach((header) => {
7574
res.setHeader(header.key, header.value);
7675
});
7776
});

src/middleware/index.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2020
*/
2121

22-
const _ = require("lodash");
23-
2422
[
2523
"protect",
2624
"redirects",
@@ -31,7 +29,7 @@ const _ = require("lodash");
3129
"missing",
3230
].forEach((name) => {
3331
exports[name] = function (spec, config) {
34-
const mware = require("./" + _.kebabCase(name))(spec, config);
32+
const mware = require("./" + name)(spec, config);
3533
mware._name = name;
3634
return mware;
3735
};

src/middleware/redirects.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
*/
2121

2222
const isUrl = require("is-url");
23-
const _ = require("lodash");
2423

2524
const patterns = require("../utils/patterns");
2625
const pathToRegexp = require("path-to-regexp");
@@ -117,13 +116,13 @@ Redirect.prototype.test = function (url) {
117116

118117
module.exports = function () {
119118
return function (req, res, next) {
120-
const config = _.get(req, "superstatic.redirects");
119+
const config = req?.superstatic?.redirects;
121120
if (!config) {
122121
return next();
123122
}
124123

125124
const redirects = [];
126-
if (_.isArray(config)) {
125+
if (Array.isArray(config)) {
127126
config.forEach((redir) => {
128127
const glob = redir.glob ?? redir.source;
129128
redirects.push(

src/responder.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2020
*/
2121

22-
const _ = require("lodash");
2322
const mime = require("mime-types");
23+
const { isPlainObject } = require("./utils/objectutils");
2424
const path = require("path");
2525
const onFinished = require("on-finished");
2626
const destroy = require("destroy");
@@ -72,11 +72,11 @@ Responder.prototype.handle = function (item, next) {
7272
};
7373

7474
Responder.prototype._handle = function (item) {
75-
if (_.isArray(item)) {
75+
if (Array.isArray(item)) {
7676
return this.handleStack(item);
77-
} else if (_.isString(item)) {
77+
} else if (typeof item === "string") {
7878
return this.handleFile({ file: item });
79-
} else if (_.isPlainObject(item)) {
79+
} else if (isPlainObject(item)) {
8080
if (item.file) {
8181
return this.handleFile(item);
8282
} else if (item.redirect) {
@@ -86,7 +86,7 @@ Responder.prototype._handle = function (item) {
8686
} else if (item.data) {
8787
return this.handleData(item);
8888
}
89-
} else if (_.isFunction(item)) {
89+
} else if (typeof item === "function") {
9090
return this.handleMiddleware(item);
9191
}
9292

src/superstatic.js

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2020
*/
2121

22-
const _ = require("lodash");
2322
const makerouter = require("router");
2423

2524
const fsProvider = require("./providers/fs");
@@ -58,18 +57,11 @@ const superstatic = function (spec = {}) {
5857
// Set up provider
5958
const provider = spec.provider
6059
? promiseback(spec.provider, 2)
61-
: fsProvider(
62-
_.extend(
63-
{
64-
cwd: cwd, // default current working directory
65-
},
66-
config,
67-
),
68-
);
60+
: fsProvider({ cwd, ...config });
6961

7062
// Select compression middleware
7163
let compressor;
72-
if (_.isFunction(spec.compression)) {
64+
if (typeof spec.compression === "function") {
7365
compressor = spec.compression;
7466
} else if (spec.compression ?? spec.gzip) {
7567
compressor = defaultCompressor;

0 commit comments

Comments
 (0)