From e120231d662e29bbd40d885cba017db2417aed71 Mon Sep 17 00:00:00 2001 From: Sukka Date: Wed, 2 Aug 2023 20:56:32 +0800 Subject: [PATCH] Reduce the usage of lodash (#505) * Reduce the usage of lodash * Add missing comment back * Update unreleased changelog * Add missing comment back * Fix misplaced changelog * Fix edge cases * Fix viewer.js tests * Make ESLint Happy --- CHANGELOG.md | 3 +++ client/components/ModuleItem.jsx | 3 ++- src/analyzer.js | 22 +++++++++++++++------- src/parseUtils.js | 11 ++++------- src/tree/BaseFolder.js | 4 ++-- src/tree/ConcatenatedModule.js | 3 +-- src/tree/Folder.js | 5 ++--- src/tree/Module.js | 3 +-- src/tree/utils.js | 6 ++---- src/utils.js | 9 +++------ src/viewer.js | 2 +- test/analyzer.js | 11 +++++------ test/helpers.js | 2 +- test/parseUtils.js | 3 +-- test/plugin.js | 3 +-- 15 files changed, 44 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5919a07..6cf87184 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ _Note: Gaps between patch versions are faulty, broken or test releases._ ## UNRELEASED +* **Internal** + * Replace some lodash usages with JavaScript native API ([#505](https://github.com/webpack-contrib/webpack-bundle-analyzer/pull/505)) by [@sukkaw](https://github.com/sukkaw). + ## 4.9.0 * **Improvement** diff --git a/client/components/ModuleItem.jsx b/client/components/ModuleItem.jsx index 4927e212..2dd2c358 100644 --- a/client/components/ModuleItem.jsx +++ b/client/components/ModuleItem.jsx @@ -73,7 +73,8 @@ export default class ModuleItem extends PureComponent { } get invisibleHint() { - return `${_.upperFirst(this.itemType)} is not rendered in the treemap because it's too small.`; + const itemType = this.itemType.charAt(0).toUpperCase() + this.itemType.slice(1); + return `${itemType} is not rendered in the treemap because it's too small.`; } get isVisible() { diff --git a/src/analyzer.js b/src/analyzer.js index 14d16153..40085a70 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -27,7 +27,10 @@ function getViewerData(bundleStats, bundleDir, opts) { const isAssetIncluded = createAssetsFilter(excludeAssets); // Sometimes all the information is located in `children` array (e.g. problem in #10) - if (_.isEmpty(bundleStats.assets) && !_.isEmpty(bundleStats.children)) { + if ( + (bundleStats.assets == null || bundleStats.assets.length === 0) + && bundleStats.children && bundleStats.children.length > 0 + ) { const {children} = bundleStats; bundleStats = bundleStats.children[0]; // Sometimes if there are additional child chunks produced add them as child assets, @@ -38,7 +41,7 @@ function getViewerData(bundleStats, bundleDir, opts) { bundleStats.assets.push(asset); }); } - } else if (!_.isEmpty(bundleStats.children)) { + } else if (bundleStats.children && bundleStats.children.length > 0) { // Sometimes if there are additional child chunks produced add them as child assets bundleStats.children.forEach((child) => { child.assets.forEach((asset) => { @@ -59,7 +62,7 @@ function getViewerData(bundleStats, bundleDir, opts) { // See #22 asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, ''); - return FILENAME_EXTENSIONS.test(asset.name) && !_.isEmpty(asset.chunks) && isAssetIncluded(asset.name); + return FILENAME_EXTENSIONS.test(asset.name) && asset.chunks.length > 0 && isAssetIncluded(asset.name); }); // Trying to parse bundle assets and get real module sizes if `bundleDir` is provided @@ -82,11 +85,14 @@ function getViewerData(bundleStats, bundleDir, opts) { continue; } - bundlesSources[statAsset.name] = _.pick(bundleInfo, 'src', 'runtimeSrc'); + bundlesSources[statAsset.name] = { + src: bundleInfo.src, + runtimeSrc: bundleInfo.runtimeSrc + }; Object.assign(parsedModules, bundleInfo.modules); } - if (_.isEmpty(bundlesSources)) { + if (Object.keys(bundlesSources).length === 0) { bundlesSources = null; parsedModules = null; logger.warn('\nNo bundles were parsed. Analyzer will show only original module sizes from stats file.\n'); @@ -97,8 +103,10 @@ function getViewerData(bundleStats, bundleDir, opts) { // If asset is a childAsset, then calculate appropriate bundle modules by looking through stats.children const assetBundles = statAsset.isChild ? getChildAssetBundles(bundleStats, statAsset.name) : bundleStats; const modules = assetBundles ? getBundleModules(assetBundles) : []; - const asset = result[statAsset.name] = _.pick(statAsset, 'size'); - const assetSources = bundlesSources && _.has(bundlesSources, statAsset.name) ? + const asset = result[statAsset.name] = { + size: statAsset.size + }; + const assetSources = bundlesSources && Object.prototype.hasOwnProperty.call(bundlesSources, statAsset.name) ? bundlesSources[statAsset.name] : null; if (assetSources) { diff --git a/src/parseUtils.js b/src/parseUtils.js index ec4ec2ab..cca8c0ae 100644 --- a/src/parseUtils.js +++ b/src/parseUtils.js @@ -1,5 +1,4 @@ const fs = require('fs'); -const _ = require('lodash'); const acorn = require('acorn'); const walk = require('acorn-walk'); @@ -140,14 +139,12 @@ function parseBundle(bundlePath) { } ); - let modules; + const modules = {}; if (walkState.locations) { - modules = _.mapValues(walkState.locations, - loc => content.slice(loc.start, loc.end) - ); - } else { - modules = {}; + Object.entries(walkState.locations).forEach(([id, loc]) => { + modules[id] = content.slice(loc.start, loc.end); + }); } return { diff --git a/src/tree/BaseFolder.js b/src/tree/BaseFolder.js index aa0a0032..60bcb212 100644 --- a/src/tree/BaseFolder.js +++ b/src/tree/BaseFolder.js @@ -10,7 +10,7 @@ export default class BaseFolder extends Node { } get src() { - if (!_.has(this, '_src')) { + if (!Object.prototype.hasOwnProperty.call(this, '_src')) { this._src = this.walk((node, src) => (src += node.src || ''), '', false); } @@ -18,7 +18,7 @@ export default class BaseFolder extends Node { } get size() { - if (!_.has(this, '_size')) { + if (!Object.prototype.hasOwnProperty.call(this, '_size')) { this._size = this.walk((node, size) => (size + node.size), 0, false); } diff --git a/src/tree/ConcatenatedModule.js b/src/tree/ConcatenatedModule.js index ba90f46f..4446ab32 100644 --- a/src/tree/ConcatenatedModule.js +++ b/src/tree/ConcatenatedModule.js @@ -1,5 +1,4 @@ import _ from 'lodash'; - import Module from './Module'; import ContentModule from './ContentModule'; import ContentFolder from './ContentFolder'; @@ -41,7 +40,7 @@ export default class ConcatenatedModule extends Module { return; } - const [folders, fileName] = [pathParts.slice(0, -1), _.last(pathParts)]; + const [folders, fileName] = [pathParts.slice(0, -1), pathParts[pathParts.length - 1]]; let currentFolder = this; folders.forEach(folderName => { diff --git a/src/tree/Folder.js b/src/tree/Folder.js index 9bcbc006..e35347f5 100644 --- a/src/tree/Folder.js +++ b/src/tree/Folder.js @@ -1,4 +1,3 @@ -import _ from 'lodash'; import gzipSize from 'gzip-size'; import Module from './Module'; @@ -13,7 +12,7 @@ export default class Folder extends BaseFolder { } get gzipSize() { - if (!_.has(this, '_gzipSize')) { + if (!Object.prototype.hasOwnProperty.call(this, '_gzipSize')) { this._gzipSize = this.src ? gzipSize.sync(this.src) : 0; } @@ -27,7 +26,7 @@ export default class Folder extends BaseFolder { return; } - const [folders, fileName] = [pathParts.slice(0, -1), _.last(pathParts)]; + const [folders, fileName] = [pathParts.slice(0, -1), pathParts[pathParts.length - 1]]; let currentFolder = this; folders.forEach(folderName => { diff --git a/src/tree/Module.js b/src/tree/Module.js index 1ee89620..f615c7cc 100644 --- a/src/tree/Module.js +++ b/src/tree/Module.js @@ -1,4 +1,3 @@ -import _ from 'lodash'; import gzipSize from 'gzip-size'; import Node from './Node'; @@ -40,7 +39,7 @@ export default class Module extends Node { } getGzipSize() { - if (!_.has(this, '_gzipSize')) { + if (!('_gzipSize' in this)) { this._gzipSize = this.src ? gzipSize.sync(this.src) : undefined; } diff --git a/src/tree/utils.js b/src/tree/utils.js index 0b09726e..a997e082 100644 --- a/src/tree/utils.js +++ b/src/tree/utils.js @@ -1,5 +1,3 @@ -import _ from 'lodash'; - const MULTI_MODULE_REGEXP = /^multi /u; export function getModulePathParts(moduleData) { @@ -7,9 +5,9 @@ export function getModulePathParts(moduleData) { return [moduleData.identifier]; } - const parsedPath = _ + const loaders = moduleData.name.split('!'); // Removing loaders from module path: they're joined by `!` and the last part is a raw module path - .last(moduleData.name.split('!')) + const parsedPath = loaders[loaders.length - 1] // Splitting module path into parts .split('/') // Removing first `.` diff --git a/src/utils.js b/src/utils.js index 5886871a..d5155ffa 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,4 @@ const {inspect, types} = require('util'); -const _ = require('lodash'); const opener = require('opener'); const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; @@ -7,9 +6,8 @@ const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', ' exports.createAssetsFilter = createAssetsFilter; function createAssetsFilter(excludePatterns) { - const excludeFunctions = _(excludePatterns) - .castArray() - .compact() + const excludeFunctions = (Array.isArray(excludePatterns) ? excludePatterns : [excludePatterns]) + .filter(Boolean) .map(pattern => { if (typeof pattern === 'string') { pattern = new RegExp(pattern, 'u'); @@ -26,8 +24,7 @@ function createAssetsFilter(excludePatterns) { } return pattern; - }) - .value(); + }); if (excludeFunctions.length) { return (asset) => excludeFunctions.every(fn => fn(asset) !== true); diff --git a/src/viewer.js b/src/viewer.js index 53fc2589..8d482d7d 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -190,7 +190,7 @@ function getChartData(analyzerOpts, ...args) { chartData = null; } - if (_.isPlainObject(chartData) && _.isEmpty(chartData)) { + if (_.isPlainObject(chartData) && Object.keys(chartData).length === 0) { logger.error("Could't find any javascript bundles in provided stats file"); chartData = null; } diff --git a/test/analyzer.js b/test/analyzer.js index 433cf4f6..89889be9 100644 --- a/test/analyzer.js +++ b/test/analyzer.js @@ -1,7 +1,6 @@ const chai = require('chai'); chai.use(require('chai-subset')); const {expect} = chai; -const _ = require('lodash'); const fs = require('fs'); const path = require('path'); const del = require('del'); @@ -107,7 +106,7 @@ describe('Analyzer', function () { it('should gracefully parse invalid chunks', async function () { generateReportFrom('with-invalid-chunk/stats.json'); const chartData = await getChartData(); - const invalidChunk = _.find(chartData, {label: 'invalid-chunk.js'}); + const invalidChunk = chartData.find(i => i.label === 'invalid-chunk.js'); expect(invalidChunk.groups).to.containSubset([ { id: 1, @@ -123,14 +122,14 @@ describe('Analyzer', function () { it('should gracefully process missing chunks', async function () { generateReportFrom('with-missing-chunk/stats.json'); const chartData = await getChartData(); - const invalidChunk = _.find(chartData, {label: 'invalid-chunk.js'}); + const invalidChunk = chartData.find(i => i.label === 'invalid-chunk.js'); expect(invalidChunk).to.exist; expect(invalidChunk.statSize).to.equal(24); forEachChartItem([invalidChunk], item => { expect(typeof item.statSize).to.equal('number'); expect(item.parsedSize).to.be.undefined; }); - const validChunk = _.find(chartData, {label: 'valid-chunk.js'}); + const validChunk = chartData.find(i => i.label === 'valid-chunk.js'); forEachChartItem([validChunk], item => { expect(typeof item.statSize).to.equal('number'); expect(typeof item.parsedSize).to.equal('number'); @@ -140,14 +139,14 @@ describe('Analyzer', function () { it('should gracefully process missing module chunks', async function () { generateReportFrom('with-missing-module-chunks/stats.json'); const chartData = await getChartData(); - const invalidChunk = _.find(chartData, {label: 'invalid-chunk.js'}); + const invalidChunk = chartData.find(i => i.label === 'invalid-chunk.js'); expect(invalidChunk).to.exist; expect(invalidChunk.statSize).to.equal(568); forEachChartItem([invalidChunk], item => { expect(typeof item.statSize).to.equal('number'); expect(item.parsedSize).to.be.undefined; }); - const validChunk = _.find(chartData, {label: 'valid-chunk.js'}); + const validChunk = chartData.find(i => i.label === 'valid-chunk.js'); forEachChartItem([validChunk], item => { expect(typeof item.statSize).to.equal('number'); expect(typeof item.parsedSize).to.equal('number'); diff --git a/test/helpers.js b/test/helpers.js index 138db1fa..973ded75 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -17,7 +17,7 @@ const getAvailableWebpackVersions = _.memoize(() => function forEachWebpackVersion(versions, cb) { const availableVersions = getAvailableWebpackVersions(); - if (_.isFunction(versions)) { + if (typeof versions === 'function') { cb = versions; versions = availableVersions; } else { diff --git a/test/parseUtils.js b/test/parseUtils.js index a7915577..5893d623 100644 --- a/test/parseUtils.js +++ b/test/parseUtils.js @@ -3,7 +3,6 @@ chai.use(require('chai-subset')); const {expect} = chai; const fs = require('fs'); -const _ = require('lodash'); const {parseBundle} = require('../lib/parseUtils'); const BUNDLES_DIR = `${__dirname}/bundles`; @@ -17,7 +16,7 @@ describe('parseBundle', function () { bundles .filter(bundleName => bundleName.startsWith('valid')) .forEach(bundleName => { - it(`should parse ${_.lowerCase(bundleName)}`, function () { + it(`should parse ${bundleName.toLocaleLowerCase()}`, function () { const bundleFile = `${BUNDLES_DIR}/${bundleName}.js`; const bundle = parseBundle(bundleFile); const expectedModules = JSON.parse(fs.readFileSync(`${BUNDLES_DIR}/${bundleName}.modules.json`)); diff --git a/test/plugin.js b/test/plugin.js index a34b6785..72f82ab9 100644 --- a/test/plugin.js +++ b/test/plugin.js @@ -3,7 +3,6 @@ chai.use(require('chai-subset')); const {expect} = chai; const fs = require('fs'); const del = require('del'); -const _ = require('lodash'); const path = require('path'); const puppeteer = require('puppeteer'); const BundleAnalyzerPlugin = require('../lib/BundleAnalyzerPlugin'); @@ -113,7 +112,7 @@ describe('Plugin', function () { await webpackCompile(config); const chartData = await getChartDataFromReport(); - expect(_.map(chartData, 'label')) + expect(chartData.map(i => i.label)) .to .deep .equal(['bundle.js']);