DEV Community

Split Blog for Split Software

Posted on • Originally published at split.io on

The Journey to Improve our Web SDKs

When the Split JavaScript SDK was first designed, our priority was to have an SDK that was easy to integrate into any application, and compatible with as much of the JavaScript ecosystem as possible.

And as a startup, Split also had its list of priorities, including being able to iterate quickly, keep costs low, and coordinate between a team of developers that spoke multiple languages (and we don’t just mean programming languages!).

Throughout the journey of building and growing our JavaScript SDK, we were able to achieve a 45% reduction in size with our new Browser SDK vs. our full SDK! This was especially challenging given all the powerful capabilities we’ve jam-packed into our SDK along the way:

  • Designing a consistent architecture to be implemented in all languages and a language-agnostic specification to follow through, as closely as it made sense given platform differences.
  • Implementing a LocalStorage caching option, to support persistent browser caching.
  • Support for a localhost mode, so people can easily write tests for features that use flags.
  • Performing feature flag evaluations at the SDK level to keep end-user data secure and enable targeting by user attributes without requiring PII be sent to Split.
  • Informing the user of invalid inputs or possible misconfiguration (e.g. if multiple instances of the same config are created) to help development teams diagnose issues faster.
  • Adding streaming support – a feature that ensures that changes made in the backend are reflected in the browser in near real-time. How neat is it to propagate flag changes incredibly fast?

The performance and resilience of our SDK were always top of mind, and throughout the past few years, we’ve worked on many improvements. But as we continued to provide additional features that met the needs and requests of customers, we realized that the SDK was becoming increasingly unwieldy. This contributed to higher page load times, negatively impacting user experience and subsequently SEO ranking.

So we set out to analyze and make changes to decrease the SDK size while maintaining functionality.

Step 1: Measuring and chipping away at major offenders

We started by measuring different builds and combinations. We analyzed the output with tools like Webpack bundle analyzer and tried to identify the biggest modules we could cut off.

We made a number of adjustments:

  • We removed our HTTP library Axios, which served us well for quite some time as a strong isomorphic solution, in favor of native fetch for browsers and node-fetch on the server-side.
  • We tweaked our build to the detail and removed our regenerator-runtime dependency at the expense of not using some of the new features like async/await in our source code.
  • We validated no splits still active with our oldest hashing algorithm so we cleaned it up too.
  • We started looking for verbose implementations to improve by refactoring the code.
  • We even looked at how the syntax that is not implemented in our range of supported browsers is finally transpiled and stopped using the ones that consumed unnecessary bits. Or moved a few things around to favor minification of variable names.

These efforts spanned a few months and in between we’ve added features like streaming support (increasing the amount of code), but from the peak to today we’re at around 58% of the original size!

But while we could continue to make incremental changes, the impact was going to be small. We had to face the fact that the SDK had a lot of functionality and the biggest chunk left was just source code.

While we still had goals to hit, which included iterating our solutions quickly, ensuring effective maintenance, serving a rich API while continuing our reach into the JavaScript ecosystem, and making solutions like our React SDK or the Split Evaluator reuse our code, we realized that we needed to break down our solution into different modules to plug and reuse in different forms.

Step 2: Modularizing the SDK

We had explored this approach before with our Golang implementations, where we reuse different modules from both the Go SDK and the Split Synchronizer, as well as even some internal microservices. This approach has been working great!

Analyzing our current situation, we needed to determine how to keep our APIs from unexpected changes as well as to more securely integrate them from consumer libraries by different developers. We needed support for types. (There are other advantages to having types, like helping avoid undetectable issues of a big and complex dynamic typing ecosystem of modules and utilities.)

We chose to use Typescript, a language we already knew and for which we already had type definitions to use as a starting point. Besides, being statically typed, there’s no impact in our bundles 😊

Once we had a clear path forward, we migrated all functionality into different modules aimed to be consumed from different implementations and enabled tree shaking (removal of dead code) as one of the main drivers. Storage implementations, synchronization mechanisms, input validation utilities, logging functionality—everything was moved and tweaked to be reusable modules.

We moved all tests and this time around we chose Jest as the testing framework of choice. It’s a fast, extensible, robust testing framework that comes with assertions and anything you need and it’s also great to test code in a platform-agnostic environment.

After we had these modules ready, our new javascript-commons project, we can now ship the first SDK powered by them!

Step 3: Creating a pluggable Web SDK

We had one final aspiration in breaking down our SDK into different modules. Having the web as the target and still aiming to reduce bundle sizes, we wanted to offer a pluggable API so customers and ourselves (for our UMD prebuilt bundles stored on Split’s CDN, as well as different JavaScript based libraries) can take advantage of tree shaking and avoid unnecessary code!

We’ve been working on a JavaScript Browser Client SDK powered by our new modules, respecting the API we always had as much as possible keeping a very similar config object and exposing the same methods, but with a few subtle differences. Let’s see an example!

With the regular SDK, if you want to use the browser LocalStorage as a cache, given you always have the code available, all you need is to pass a string constant and an optional prefix if desired.


`// With javascript-client
import { SplitFactory } from '@splitsoftware/splitio';

const factory = SplitFactory({
 core: {
   authorizationKey: 'YOUR_API_KEY',
   key: 'YOUR_KEY',
 },
 storage: {
   type: 'LOCALSTORAGE',
   prefix: 'MYPREFIX'
 }
});`
<small id="shcb-language-1"><span>Code language:</span> <span>Go</span> <span>(</span><span>go</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

While this is a commodity, if you don’t use that cache, you still have it’s implementation code in your bundle.

With the new SDK, even though the pluggable API is not part of the modules imported by the entry point, you can plug it in a breeze!


`// With javascript-browser-client
import { SplitFactory, InLocalStorage } from '@splitsoftware/splitio-browserjs';

const factory = SplitFactory({
 core: {
   authorizationKey: 'YOUR_API_KEY',
   key: 'YOUR_KEY',
 },
 storage: InLocalStorage({
   prefix: 'MYPREFIX'
 })
});`
<small id="shcb-language-2"><span>Code language:</span> <span>JavaScript</span> <span>(</span><span>javascript</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

Another example are the integrations. Same situation as before, with the regular SDK we have the code always available so we can set them up with a constant:


`// With javascript-client
import { SplitFactory } from '@splitsoftware/splitio';

const factory = SplitFactory({
 core: {
   authorizationKey: 'YOUR_API_KEY',
   key: 'YOUR_KEY'
 },
 integrations: [{
     type: 'AN_INTEGRATION_TYPE'
   }, {
     type: 'ANOTHER_INTEGRATION_TYPE',
     // integration specific options would go here
     ...
   }
 ]
});`
<small id="shcb-language-3"><span>Code language:</span> <span>Scala</span> <span>(</span><span>scala</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

In the new SDK, these are also optional:


`// With javascript-browser-client
import { SplitFactory, AnIntegrationType, AnotherIntegrationType } from '@splitsoftware/splitio-browserjs';

const factory = SplitFactory({
 core: {
   authorizationKey: 'YOUR_API_KEY',
   key: 'YOUR_KEY'
 },
 integrations: [
   AnIntegrationType(),
   AnotherIntegrationType({
     // integration specific options would go here
     ...
   })
 ]
});`
<small id="shcb-language-4"><span>Code language:</span> <span>JavaScript</span> <span>(</span><span>javascript</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

While we only support two Google Analytics implementations (i.e. sending Split data GA and the other way around) as a first class SDK to SDK integration at this time, we’ve implemented those on top of a generic framework so please let us know if there’s a particular one you want to see!

The logger is also part of the first pluggable module the SDK supports as it allows us to extract the bulk of the log message strings, yielding great results when it comes to reducing size of the code. And we’ll keep “plugging” away at making additional modules pluggable for our customers to leverage in the future.

Step 4: Measuring the impact of our changes

When comparing the full blown SDK with full functionality versus the new Browser SDK in its now slimmer and trimmer state, we have managed to reduce up to 45% of the impact in the bundles! And If we compare the JS SDK in its heaviest version to the minimal Browser SDK, there’s more than 100% improvement ! 🤯

And the cool thing is that you don’t need to choose between the minimal and full versions. You can start small and add what you want on top of it. If you’re not sure, you can start with the full set of features and then if you find yourself not using most of the features, migration will be easy enough.

Final thoughts

We will continue to maintain the isomorphic JavaScript SDK and API, eventually being powered by our modules. The SDK is still relevant and it’s a safe bet for most cases where you can afford the extra bits in your bundle, especially if the page where you load the SDK doesn’t require to squeeze the maximum out of your bundle size optimization.

In exchange you get a full blown API supporting all features, without any extra hassle to ensure you’ve configured what you want to use as well as support for IE11 out-of-the-box.

And while the spring cleaning that we did with our SDK happened to coincide with the start of spring, we’ll continue to evaluate additional updates, including more pluggable modules, support for generic integrations and other ideas.

If you’re working on a new Web application or improving your page load times, try out our new Web SDK!

Learn more about what you can do with our JavaScript and other SDKs:

And as always, we’d love to have you follow along as we share new features and stories. Check us out on YouTube, Twitter, and LinkedIn!

Top comments (0)