From 5cf5ddc4f21b3174e2501251d4d82bb5d932fb2c Mon Sep 17 00:00:00 2001 From: fayyazarshad <42437293+fayyazarshad@users.noreply.github.com> Date: Mon, 9 Mar 2020 20:15:14 +0500 Subject: [PATCH] refactor: Removed cloneDeep dependency from the sdk package (#415) Summary: To reduce package size, we are gradually removing lodash library. This PR removes lodash.cloneDeep from optimizely-sdk package. It is still part of the repository but it is only being used to run tests. lodash has been removed from dependencies and moved to dev-dependencies in package.json Test plan: All unit tests and Full Stack compatibility tests pass after this change Co-authored-by: zashraf1985 <35262377+zashraf1985@users.noreply.github.com> --- .../lib/core/bucketer/index.tests.js | 10 ++++----- .../lib/core/decision_service/index.js | 7 +++---- .../lib/core/decision_service/index.tests.js | 18 ++++++++-------- .../lib/core/optimizely_config/index.tests.js | 4 ++-- .../lib/core/project_config/index.js | 8 +++---- .../lib/core/project_config/index.tests.js | 21 ++++++++++--------- .../project_config_manager.tests.js | 12 ++++++----- .../optimizely-sdk/lib/index.browser.tests.js | 1 - .../lib/optimizely/index.tests.js | 7 ++++--- .../lib/utils/attributes_validator/index.js | 5 ++--- .../optimizely-sdk/lib/utils/fns/index.js | 1 - packages/optimizely-sdk/package-lock.json | 3 ++- packages/optimizely-sdk/package.json | 4 ++-- 13 files changed, 51 insertions(+), 50 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/bucketer/index.tests.js b/packages/optimizely-sdk/lib/core/bucketer/index.tests.js index f9afb920e..e0a142a89 100644 --- a/packages/optimizely-sdk/lib/core/bucketer/index.tests.js +++ b/packages/optimizely-sdk/lib/core/bucketer/index.tests.js @@ -47,7 +47,7 @@ describe('lib/core/bucketer', function() { describe('return values for bucketing (excluding groups)', function() { beforeEach(function() { - configObj = projectConfig.createProjectConfig(testData); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); bucketerParams = { experimentId: configObj.experiments[0].id, experimentKey: configObj.experiments[0].key, @@ -103,7 +103,7 @@ describe('lib/core/bucketer', function() { describe('return values for bucketing (including groups)', function() { var bucketerStub; beforeEach(function() { - configObj = projectConfig.createProjectConfig(testData); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); bucketerParams = { experimentId: configObj.experiments[0].id, experimentKey: configObj.experiments[0].key, @@ -284,7 +284,7 @@ describe('lib/core/bucketer', function() { describe('when the bucket value falls into empty traffic allocation ranges', function() { beforeEach(function() { - configObj = projectConfig.createProjectConfig(testData); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); bucketerParams = { experimentId: configObj.experiments[0].id, experimentKey: configObj.experiments[0].key, @@ -314,7 +314,7 @@ describe('lib/core/bucketer', function() { describe('when the traffic allocation has invalid variation ids', function() { beforeEach(function() { - configObj = projectConfig.createProjectConfig(testData); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); bucketerParams = { experimentId: configObj.experiments[0].id, experimentKey: configObj.experiments[0].key, @@ -371,7 +371,7 @@ describe('lib/core/bucketer', function() { }); beforeEach(function() { - configObj = projectConfig.createProjectConfig(testData); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); bucketerParams = { trafficAllocationConfig: configObj.experiments[0].trafficAllocation, variationIdMap: configObj.variationIdMap, diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.js b/packages/optimizely-sdk/lib/core/decision_service/index.js index f0d1e4e25..245658f9e 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.js +++ b/packages/optimizely-sdk/lib/core/decision_service/index.js @@ -300,14 +300,13 @@ DecisionService.prototype.__saveUserProfile = function(experiment, variation, us } try { - var newBucketMap = fns.cloneDeep(experimentBucketMap); - newBucketMap[experiment.id] = { - variation_id: variation.id, + experimentBucketMap[experiment.id] = { + variation_id: variation.id }; this.userProfileService.save({ user_id: userId, - experiment_bucket_map: newBucketMap, + experiment_bucket_map: experimentBucketMap, }); this.logger.log( diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.tests.js b/packages/optimizely-sdk/lib/core/decision_service/index.tests.js index ae661df5f..4f5c01675 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.tests.js +++ b/packages/optimizely-sdk/lib/core/decision_service/index.tests.js @@ -21,7 +21,7 @@ var errorHandler = require('../../plugins/error_handler'); var bucketer = require('../bucketer'); var DecisionService = require('./'); var enums = require('../../utils/enums'); -var fns = require('../../utils/fns'); +var cloneDeep = require('lodash/cloneDeep'); var logger = require('../../plugins/logger'); var projectConfig = require('../project_config'); var sprintf = require('@optimizely/js-sdk-utils').sprintf; @@ -40,7 +40,7 @@ var DECISION_SOURCES = enums.DECISION_SOURCES; describe('lib/core/decision_service', function() { describe('APIs', function() { - var configObj = projectConfig.createProjectConfig(testData); + var configObj = projectConfig.createProjectConfig(cloneDeep(testData)); var decisionServiceInstance; var mockLogger = logger.createLogger({ logLevel: LOG_LEVEL.INFO }); var bucketerStub; @@ -860,7 +860,7 @@ describe('lib/core/decision_service', function() { 'control' ); assert.strictEqual(didSetVariation, true); - var newDatafile = fns.cloneDeep(testData); + var newDatafile = cloneDeep(testData); // Remove 'control' variation from variations, traffic allocation, and datafile forcedVariations. newDatafile.experiments[0].variations = [ { @@ -892,7 +892,7 @@ describe('lib/core/decision_service', function() { 'control' ); assert.strictEqual(didSetVariation, true); - var newConfigObj = projectConfig.createProjectConfig(testDataWithFeatures); + var newConfigObj = projectConfig.createProjectConfig(cloneDeep(testDataWithFeatures)); var forcedVar = decisionServiceInstance.getForcedVariation(newConfigObj, 'testExperiment', 'user1'); assert.strictEqual(forcedVar, null); }); @@ -917,7 +917,7 @@ describe('lib/core/decision_service', function() { // TODO: Move tests that test methods of Optimizely to lib/optimizely/index.tests.js describe('when a bucketingID is provided', function() { - var configObj = projectConfig.createProjectConfig(testData); + var configObj = projectConfig.createProjectConfig(cloneDeep(testData)); var createdLogger = logger.createLogger({ logLevel: LOG_LEVEL.DEBUG, logToConsole: false, @@ -926,7 +926,7 @@ describe('lib/core/decision_service', function() { beforeEach(function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData, + datafile: cloneDeep(testData), jsonSchemaValidator: jsonSchemaValidator, isValidInstance: true, logger: createdLogger, @@ -1050,7 +1050,7 @@ describe('lib/core/decision_service', function() { beforeEach(function() { sinon.stub(mockLogger, 'log'); - configObj = projectConfig.createProjectConfig(testData); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); decisionService = DecisionService.createDecisionService({ logger: mockLogger, }); @@ -1088,7 +1088,7 @@ describe('lib/core/decision_service', function() { var sandbox; var mockLogger = logger.createLogger({ logLevel: LOG_LEVEL.INFO }); beforeEach(function() { - configObj = projectConfig.createProjectConfig(testDataWithFeatures); + configObj = projectConfig.createProjectConfig(cloneDeep(testDataWithFeatures)); sandbox = sinon.sandbox.create(); sandbox.stub(mockLogger, 'log'); decisionServiceInstance = DecisionService.createDecisionService({ @@ -1978,7 +1978,7 @@ describe('lib/core/decision_service', function() { var __buildBucketerParamsSpy; beforeEach(function() { - configObj = projectConfig.createProjectConfig(testDataWithFeatures); + configObj = projectConfig.createProjectConfig(cloneDeep(testDataWithFeatures)); feature = configObj.featureKeyMap.test_feature; decisionService = DecisionService.createDecisionService({ logger: logger.createLogger({ logLevel: LOG_LEVEL.INFO }), diff --git a/packages/optimizely-sdk/lib/core/optimizely_config/index.tests.js b/packages/optimizely-sdk/lib/core/optimizely_config/index.tests.js index 5aedc01ac..bcacbb233 100644 --- a/packages/optimizely-sdk/lib/core/optimizely_config/index.tests.js +++ b/packages/optimizely-sdk/lib/core/optimizely_config/index.tests.js @@ -14,7 +14,7 @@ * limitations under the License. */ var assert = require('chai').assert; - +var cloneDeep = require('lodash/cloneDeep'); var datafile = require('../../tests/test_data').getTestProjectConfigWithFeatures(); var projectConfig = require('../project_config'); var optimizelyConfig = require('./index'); @@ -37,7 +37,7 @@ describe('lib/core/optimizely_config', function() { var optimizelyConfigObject; var projectConfigObject; beforeEach(function() { - projectConfigObject = projectConfig.createProjectConfig(datafile); + projectConfigObject = projectConfig.createProjectConfig(cloneDeep(datafile)); optimizelyConfigObject = optimizelyConfig.getOptimizelyConfig(projectConfigObject); }); diff --git a/packages/optimizely-sdk/lib/core/project_config/index.js b/packages/optimizely-sdk/lib/core/project_config/index.js index 61bf2ad40..3f26f2376 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.js +++ b/packages/optimizely-sdk/lib/core/project_config/index.js @@ -35,7 +35,7 @@ module.exports = { * @return {Object} Object representing project configuration */ createProjectConfig: function(datafile) { - var projectConfig = fns.cloneDeep(datafile); + var projectConfig = fns.assign({}, datafile); /* * Conditions of audiences in projectConfig.typedAudiences are not @@ -53,16 +53,16 @@ module.exports = { var experiments; Object.keys(projectConfig.groupIdMap || {}).forEach(function(Id) { - experiments = fns.cloneDeep(projectConfig.groupIdMap[Id].experiments); + experiments = projectConfig.groupIdMap[Id].experiments; (experiments || []).forEach(function(experiment) { projectConfig.experiments.push(fns.assign(experiment, { groupId: Id })); }); }); projectConfig.rolloutIdMap = fns.keyBy(projectConfig.rollouts || [], 'id'); - jsSdkUtils.objectValues(projectConfig.rolloutIdMap || {}).forEach(function(rollout) { + jsSdkUtils.objectValues(projectConfig.rolloutIdMap || {}).forEach(function (rollout) { (rollout.experiments || []).forEach(function(experiment) { - projectConfig.experiments.push(fns.cloneDeep(experiment)); + projectConfig.experiments.push(experiment); // Creates { : } map inside of the experiment experiment.variationKeyMap = fns.keyBy(experiment.variations, 'key'); }); diff --git a/packages/optimizely-sdk/lib/core/project_config/index.tests.js b/packages/optimizely-sdk/lib/core/project_config/index.tests.js index 64e5b73dc..3add953ab 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.tests.js +++ b/packages/optimizely-sdk/lib/core/project_config/index.tests.js @@ -21,7 +21,8 @@ var logging = require('@optimizely/js-sdk-logging'); var logger = logging.getLogger(); -var _ = require('lodash/core'); +var forEach = require('lodash/forEach'); +var cloneDeep = require('lodash/cloneDeep'); var fns = require('../../utils/fns'); var chai = require('chai'); var assert = chai.assert; @@ -40,8 +41,8 @@ describe('lib/core/project_config', function() { var testData = testDatafile.getTestProjectConfig(); var configObj = projectConfig.createProjectConfig(testData); - _.forEach(testData.audiences, function(audience) { - audience.conditions = JSON.parse(audience.conditions); + forEach(testData.audiences, function(audience) { + audience.conditions = audience.conditions; }); assert.strictEqual(configObj.accountId, testData.accountId); @@ -59,14 +60,14 @@ describe('lib/core/project_config', function() { assert.deepEqual(configObj.groupIdMap, expectedGroupIdMap); var expectedExperiments = testData.experiments; - _.forEach(configObj.groupIdMap, function(group, Id) { - _.forEach(group.experiments, function(experiment) { + forEach(configObj.groupIdMap, function(group, Id) { + forEach(group.experiments, function(experiment) { experiment.groupId = Id; expectedExperiments.push(experiment); }); }); - _.forEach(expectedExperiments, function(experiment) { + forEach(expectedExperiments, function(experiment) { experiment.variationKeyMap = fns.keyBy(experiment.variations, 'key'); }); @@ -242,12 +243,12 @@ describe('lib/core/project_config', function() { }); describe('projectConfig helper methods', function() { - var testData = testDatafile.getTestProjectConfig(); + var testData = cloneDeep(testDatafile.getTestProjectConfig()); var configObj; var createdLogger = loggerPlugin.createLogger({ logLevel: LOG_LEVEL.INFO }); beforeEach(function() { - configObj = projectConfig.createProjectConfig(testData); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); sinon.stub(createdLogger, 'log'); }); @@ -630,14 +631,14 @@ describe('lib/core/project_config', function() { describe('#getExperimentAudienceConditions', function() { it('should retrieve audiences for valid experiment key', function() { - configObj = projectConfig.createProjectConfig(testData); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); assert.deepEqual(projectConfig.getExperimentAudienceConditions(configObj, testData.experiments[1].key), [ '11154', ]); }); it('should throw error for invalid experiment key', function() { - configObj = projectConfig.createProjectConfig(testData); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); assert.throws(function() { projectConfig.getExperimentAudienceConditions(configObj, 'invalidExperimentKey'); }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, 'PROJECT_CONFIG', 'invalidExperimentKey')); diff --git a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js index ba5a0d445..0f9c42a0d 100644 --- a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js +++ b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js @@ -18,6 +18,7 @@ var assert = require('chai').assert; var datafileManager = require('@optimizely/js-sdk-datafile-manager'); var logging = require('@optimizely/js-sdk-logging'); var sinon = require('sinon'); +var cloneDeep = require('lodash/cloneDeep'); var sprintf = require('@optimizely/js-sdk-utils').sprintf; var enums = require('../../utils/enums'); var jsonSchemaValidator = require('../../utils/json_schema_validator'); @@ -156,7 +157,7 @@ describe('lib/core/project_config/project_config_manager', function() { it('should return a valid datafile from getConfig and resolve onReady with a successful result', function() { var configWithFeatures = testData.getTestProjectConfigWithFeatures(); var manager = new projectConfigManager.ProjectConfigManager({ - datafile: configWithFeatures, + datafile: cloneDeep(configWithFeatures), }); assert.deepEqual(manager.getConfig(), projectConfig.createProjectConfig(configWithFeatures)); return manager.onReady().then(function(result) { @@ -180,8 +181,9 @@ describe('lib/core/project_config/project_config_manager', function() { describe('with a datafile manager', function() { it('passes the correct options to datafile manager', function() { + var config = testData.getTestProjectConfig() new projectConfigManager.ProjectConfigManager({ - datafile: testData.getTestProjectConfig(), + datafile: config, sdkKey: '12345', datafileOptions: { autoUpdate: true, @@ -192,7 +194,7 @@ describe('lib/core/project_config/project_config_manager', function() { sinon.assert.calledWithExactly( datafileManager.HttpPollingDatafileManager, sinon.match({ - datafile: testData.getTestProjectConfig(), + datafile: config, sdkKey: '12345', autoUpdate: true, updateInterval: 10000, @@ -206,7 +208,7 @@ describe('lib/core/project_config/project_config_manager', function() { datafileManager.HttpPollingDatafileManager.returns({ start: sinon.stub(), stop: sinon.stub(), - get: sinon.stub().returns(configWithFeatures), + get: sinon.stub().returns(cloneDeep(configWithFeatures)), on: sinon.stub().returns(function() {}), onReady: sinon.stub().returns(Promise.resolve()), }); @@ -233,7 +235,7 @@ describe('lib/core/project_config/project_config_manager', function() { }); nextDatafile.revision = '36'; var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; - fakeDatafileManager.get.returns(nextDatafile); + fakeDatafileManager.get.returns(cloneDeep(nextDatafile)); var updateListener = fakeDatafileManager.on.getCall(0).args[1]; updateListener({ datafile: nextDatafile }); assert.deepEqual(manager.getConfig(), projectConfig.createProjectConfig(nextDatafile)); diff --git a/packages/optimizely-sdk/lib/index.browser.tests.js b/packages/optimizely-sdk/lib/index.browser.tests.js index ac852505d..d19746b82 100644 --- a/packages/optimizely-sdk/lib/index.browser.tests.js +++ b/packages/optimizely-sdk/lib/index.browser.tests.js @@ -25,7 +25,6 @@ var eventProcessorConfigValidator = require('./utils/event_processor_config_vali var chai = require('chai'); var assert = chai.assert; -var find = require('lodash/find'); var sinon = require('sinon'); var LocalStoragePendingEventsDispatcher = eventProcessor.LocalStoragePendingEventsDispatcher; diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index c5f8545a9..83b1e5f39 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -222,9 +222,10 @@ describe('lib/optimizely', function() { }); it('passes datafile, datafileOptions, sdkKey, and other options to the project config manager', function() { + var config = testData.getTestProjectConfig(); new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), + datafile: config, datafileOptions: { autoUpdate: true, updateInterval: 2 * 60 * 1000, @@ -239,7 +240,7 @@ describe('lib/optimizely', function() { }); sinon.assert.calledOnce(projectConfigManager.ProjectConfigManager); sinon.assert.calledWithExactly(projectConfigManager.ProjectConfigManager, { - datafile: testData.getTestProjectConfig(), + datafile: config, datafileOptions: { autoUpdate: true, updateInterval: 2 * 60 * 1000, @@ -4201,7 +4202,7 @@ describe('lib/optimizely', function() { describe('when the variation is missing the toggle', function() { beforeEach(function() { var experiment = optlyInstance.projectConfigManager.getConfig().experimentKeyMap.test_shared_feature; - var variation = fns.cloneDeep(experiment.variations[0]); + var variation = experiment.variations[0]; delete variation['featureEnabled']; sandbox.stub(optlyInstance.decisionService, 'getVariationForFeature').returns({ experiment: experiment, diff --git a/packages/optimizely-sdk/lib/utils/attributes_validator/index.js b/packages/optimizely-sdk/lib/utils/attributes_validator/index.js index e2356118b..a080e895d 100644 --- a/packages/optimizely-sdk/lib/utils/attributes_validator/index.js +++ b/packages/optimizely-sdk/lib/utils/attributes_validator/index.js @@ -19,7 +19,6 @@ */ var sprintf = require('@optimizely/js-sdk-utils').sprintf; -var lodashForOwn = require('lodash/forOwn'); var fns = require('../../utils/fns'); var ERROR_MESSAGES = require('../enums').ERROR_MESSAGES; @@ -34,8 +33,8 @@ module.exports = { */ validate: function(attributes) { if (typeof attributes === 'object' && !Array.isArray(attributes) && attributes !== null) { - lodashForOwn(attributes, function(value, key) { - if (typeof value === 'undefined') { + Object.keys(attributes).forEach(function(key) { + if (typeof attributes[key] === 'undefined') { throw new Error(sprintf(ERROR_MESSAGES.UNDEFINED_ATTRIBUTE, MODULE_NAME, key)); } }); diff --git a/packages/optimizely-sdk/lib/utils/fns/index.js b/packages/optimizely-sdk/lib/utils/fns/index.js index d584fdca0..3483019eb 100644 --- a/packages/optimizely-sdk/lib/utils/fns/index.js +++ b/packages/optimizely-sdk/lib/utils/fns/index.js @@ -39,7 +39,6 @@ module.exports = { return to; } }, - cloneDeep: require('lodash/cloneDeep'), currentTimestamp: function() { return Math.round(new Date().getTime()); }, diff --git a/packages/optimizely-sdk/package-lock.json b/packages/optimizely-sdk/package-lock.json index 1b81a2472..77390c0ab 100644 --- a/packages/optimizely-sdk/package-lock.json +++ b/packages/optimizely-sdk/package-lock.json @@ -4605,7 +4605,8 @@ "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true }, "lodash.debounce": { "version": "4.0.8", diff --git a/packages/optimizely-sdk/package.json b/packages/optimizely-sdk/package.json index d892d84f3..574005c05 100644 --- a/packages/optimizely-sdk/package.json +++ b/packages/optimizely-sdk/package.json @@ -38,7 +38,6 @@ "@optimizely/js-sdk-logging": "^0.1.0", "@optimizely/js-sdk-utils": "^0.2.0", "json-schema": "^0.2.3", - "lodash": "^4.17.11", "murmurhash": "0.0.2", "promise-polyfill": "8.1.0", "uuid": "^3.3.2" @@ -62,7 +61,8 @@ "nock": "^7.7.2", "sinon": "^2.3.1", "webpack": "^4.25.1", - "webpack-cli": "^3.1.2" + "webpack-cli": "^3.1.2", + "lodash": "^4.17.11" }, "publishConfig": { "access": "public"